2015-06-11 3 views
1

Я пишу единичный тест для одного из моих служебных объектов. В этом конкретном случае мне нужно было использовать транзакции для обеспечения целостности данных. Таким образом, у меня есть простой код:Тестирование и издевательство над содержимым блока с RSpec

class CreateUser 
    def save 
    user_klass.db.transaction do 
     user = user_klass.create(name: name, email: email) 
     another_model_klass.find_or_create(user_id: user.id, foo: 'foo') 
    end 
    end 
end 

Я использую Sequel как мой ORM. Однако важным моментом этого вопроса является, на самом деле, как проверить этот код. Я успешно использовал mocks и stubs, но это первый раз, когда я должен заглушить что-то с блоком.

В первой у меня есть наивная спецификации вроде так:

describe CreateUser do 
     describe "#save" do 
     let(:user)    { instance_double("User", id: 1) } 
     let(:user_klass)   { double("Class:User", create: user) } 
     let(:another_model_klass) { double("Class:AnotherModel") } 
     let(:name)    { 'Test User' } 
     let(:email)    { '[email protected]' } 
     let(:foo)     { 'foo' } 
     let(:params)    { { name: name, email: email, foo: foo } } 
     let!(:form)    { CreateUser.new(params, user_klass, another_model_klass) } 

     before do 
      allow(another_model_klass).to receive(:find_or_create) 
     end 

     it "sends create message to the user_klass" do 
      expect(user_klass).to receive(:create).with({ name: name, email: email}).and_return(user) 
      form.save 
     end 

     it "sends find_or_create message to another_model_klass" do 
      expect(another_model_klass).to receive(:find_or_create).with(user_id: user.id, foo: foo) 
      form.save 
     end 

     end 
    end 

Это выдает ошибку:

Double "Class:User" received unexpected message :db with (no args) 

Но если я добавлю следующее:

allow(user_klass).to receive_message_chain(:db, :transaction) 

Было бы заполнить содержимое блока транзакций, и он все равно будет терпеть неудачу.

Как сделать установленные ожидания на моей спецификации, где:

  • ожидать транзакций используется
  • ожидают создать сообщение, которое будет отправлено user_klass
  • ожидают find_or_create сообщение для another_model_klass

ответ

-1

Взгляните на шпионов https://github.com/rspec/rspec-mocks#test-spies, вы можете бросить их в своих двухместных номерах. :-)

+1

комментарий к -1 будет уместным. возьмите на себя ответственность и дайте автору понять вашу проблему с его/ее ответом. – jaydel

1

Вы можете сделать это:

let(:db) { double("DB") } 
let(:user_klass) { double("Class:User", create: user, db: db) } 
# ... 

before do 
    allow(db).to receive(:transaction).and_yield 
    # ... 
end 

Это сказал: вы можете сделать это, но я рекомендую вам этого не делают. На самом деле, я рекомендую вам не издеваться над API Sequel. Я могу говорить по опыту, что по дороге насмешливых API, которыми вы не владеете, лживые, малоценные тесты и много боли. Общий подход, рекомендуемый I (и многими другими), заключается в том, что оберните API, которому вы не владеете собственным API, который у вас есть. Затем вы можете интегрировать тестовую оболочку (без издевательств или stubbing) и макет или заглушить ваш простой, специфичный для домена API во всех других местах, которые полагаются на эту функциональность.

На стороне примечания, если вы используете RSpec 3, я настоятельно рекомендую вам переключить тестовые удваивания на verifying doubles, поскольку они предоставляют некоторые действительно хорошие гарантии, что обычные двойники не предусматривают.

+0

Я действительно борюсь с «Я могу говорить по опыту, что по дороге насмешливых API-интерфейсов вы не владеете лживыми, малоценными тестами и болью». Я не хочу писать тесты, которые используют сторонние API. Я хочу издеваться над результатами этих API, чтобы проверить пути кода, которые я написал. Я делаю это, чтобы изолировать свой код. – jaydel