Why does the "each" iterator method break rspec?
Background
I'm attempting to test my models.
app/models/user.rb
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? O开发者_StackOverflow中文版R payee_id=?", self.id, self.id])
transactions
end
end
app/models/transaction.rb
class Transaction < ActiveRecord::Base
attr_accessor :user
belongs_to :payor, class_name: 'User'
belongs_to :payee, class_name: 'User'
end
In the Transactions class, @user is an ephemeral object instance representing the user accessing the model.
spec/models/user_spec.rb
require 'spec_helper'
describe User do
let(:user) { Factory(:user) }
let(:user2) { Factory(:user) }
let(:user3) { Factory(:user) }
let(:transaction_user_user2) { Factory(:transaction, payor: user, payee: user2) }
let(:transaction_user2_user) { Factory(:transaction, payor: user2, payee: user) }
let(:transaction_user2_user3) { Factory(:transaction, payor: user2, payee: user3) }
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end
end
Using rspec 2.6.4, factory_girl 2.1.2, rails 3.1.0, ruby 1.9.2p290. As shown, the spec passes.
Problem
When I modify the transactions
method in app/models/user.rb to iterate over the results such that it reads:
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
transactions.each {|transaction| transaction.user = self}
transactions
end
end
the method transactions
now returns []
in rspec, however it works perfectly in the app views.
Since Transaction.user is ephemeral (representing the user accessing the transaction) it must be set (if it exists) every time a Transaction is initialized or built from db records.
I'm at a loss for where to begin to debug this.
All suggestions appreciated!
I think your problem lies in the fact that let
is lazy. Basically what is happening is that the transactions are not even created yet when the transactions
method is called in the test. Use let!
for a non-lazy version. See let and let! for more details.
Couldn't you just return payor_transactions + payee_transactions instead of manually selecting them?
Following the suggestion from @obrok, the solution I settled on to retain the advantage of lazy-loading let
in other tests was to touch each transaction before testing User#transactions as so:
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
[transaction_user_user2, transaction_user2_user, transaction_user2_user3].each do |transaction|
[transaction.payor_id, transaction.payee_id].each {|id| id.should_not be_nil }
end
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end
精彩评论