Почему метод «каждого» итератора нарушает rspec?

#ruby-on-rails-3 #unit-testing #rspec2 #ruby-1.9.2

#ruby-on-rails-3 #модульное тестирование #rspec2 #ruby-1.9.2

Вопрос:

Предыстория

Я пытаюсь протестировать свои модели.

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=? OR 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
  

В классе Transactions @user является экземпляром эфемерного объекта, представляющим пользователя, обращающегося к модели.

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
  

Использование rspec 2.6.4, factory_girl 2.1.2, rails 3.1.0, ruby 1.9.2p290. Как показано, спецификация проходит.

Проблема

Когда я изменяю transactions метод в 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=? OR payee_id=?", self.id, self.id])
    transactions.each {|transaction| transaction.user = self}
    transactions
  end

end
  

теперь метод transactions возвращается [] в rspec, однако он отлично работает в представлениях приложения.

Поскольку Transaction.user является эфемерным (представляющим пользователя, обращающегося к транзакции), он должен устанавливаться (если он существует) каждый раз, когда транзакция инициализируется или создается из записей БД.

Я в недоумении, с чего начать отладку этого.

Все предложения приветствуются!

Комментарии:

1. Я все еще изучаю rspec, поэтому я бы предположил, что это ошибка в моей конфигурации rspec, а не ошибка в самом rspec.

2. мой spec_helper.rb, на всякий случай: gist.github.com/1274147

Ответ №1:

Я думаю, ваша проблема заключается в том, что let он ленив. По сути, происходит то, что транзакции еще даже не созданы, когда transactions метод вызывается в тесте. Используйте let! для не ленивой версии. Смотрите let и let!для получения более подробной информации.

Ответ №2:

Не могли бы вы просто вернуть payor_transactions payee_transactions вместо того, чтобы выбирать их вручную?

Комментарии:

1. Я намерен добавить подкачку, поэтому мне нужно, чтобы объединение происходило на уровне БД, но transactions = self.payor_transactions self.payee_transactions работало бы.

2. Достаточно справедливо. Подкачка всегда усложняет задачу.

Ответ №3:

Следуя предложению @obrok, решение, на котором я остановился, чтобы сохранить преимущество отложенной загрузки let в других тестах, заключалось в том, чтобы касаться каждой транзакции перед тестированием User#transactions как таковой:

 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