RSpec + FactoryGirl should_receive не работает

Я не могу понять, почему этот тест RSpec терпит неудачу. Любой совет? Я новичок в FactoryGirl, RSpec и TDD в целом.

Контроллер:

def update
  @vendor = current_user.vendors.find(params[:id])

  if @vendor.update_attributes(params[:vendor])
    redirect_to vendor_path(@vendor)
  else
    render 'edit'
  end
end

Тестовое задание:

require 'spec_helper'

describe VendorsController do
  login_user

  before :each do
    @user = subject.current_user
    @vendor = FactoryGirl.create(:vendor, :user => @user)
  end

  [...]

  describe 'POST update' do
    def do_update
      post :update, :id => @vendor.id, :vendor => FactoryGirl.attributes_for(:vendor)
    end

    [...]

    it 'should update a given vendor' do
      do_update
      @vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
    end
  end
end

Фабрика:

FactoryGirl.define do
  factory :vendor do
    name 'Widget Vendor'
    user
  end
end

Провал:

Failures:

  1) VendorsController POST update should update a given vendor
     Failure/Error: @vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
       (#<Vendor:0x007faeb75e77d0>).update_attributes({:name=>"Widget Vendor"})
           expected: 1 time
           received: 0 times
     # ./spec/controllers/vendors_controller_spec.rb:108:in `block (3 levels) in <top (required)>'

Обновление:

Теперь я немного ближе. Я изменил тест на следующий:

it 'should update a given vendor' do
  Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
  do_update
end

И новая ошибка:

Failures:

  1) VendorsController POST update should update a given vendor
     Failure/Error: post :update, :id => @vendor.id, :vendor => FactoryGirl.attributes_for(:vendor)
       #<Vendor:0x007ff30d765900> received :update_attributes with unexpected arguments
         expected: ({:name=>"Widget Vendor"})
              got: ({"name"=>"Widget Vendor"})
     # ./app/controllers/vendors_controller.rb:33:in `update'
     # ./spec/controllers/vendors_controller_spec.rb:98:in `do_update'
     # ./spec/controllers/vendors_controller_spec.rb:108:in `block (3 levels) in <top (required)>'

Отвечать...?

Что ж, это сработало. Однако должен быть способ сделать это лучше:

Vendor.any_instance.should_receive(:update_attributes).with(JSON.parse(FactoryGirl.attributes_for(:vendor).to_json)).and_return(true)


person clem    schedule 16.11.2011    source источник
comment
В общем: уверены ли вы, что @ vendor.should_receive достаточно в качестве утверждения для обновления данного поставщика? Вы всегда вызываете update_attributes - поэтому, даже если возникает ошибка и ваш пользователь не обновляется, ваш тест будет успешным.   -  person emrass    schedule 16.11.2011
comment
Полагаю, я мог бы добавить and_return(true) в конец этой строки.   -  person clem    schedule 16.11.2011
comment
Звучит разумно (должен признать, что я тоже не эксперт в тестировании с RSpec).   -  person emrass    schedule 16.11.2011


Ответы (3)


Я думаю, что вы делаете это неправильно.

Объект @vendor в спецификациях - это еще один объект в вашем контроллере, поэтому он не получает метод update_attributes.

Вы можете попробовать это (возможно, rspec 2.5+):

Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))

Или вы можете проверить, изменились ли атрибуты объекта:

expect{
  do_update
}.to change(...)
person sparrovv    schedule 16.11.2011
comment
Это то, что я делаю сейчас, но я столкнулся с проблемой, указанной выше в конце моего вопроса. - person clem; 16.11.2011
comment
извини, я этого не заметил. Так что, возможно, вы выберете более стиль BDD и проверите, изменился ли объект с помощью метода expect, и не вызываете should_receive? - person sparrovv; 16.11.2011
comment
Я думаю, что на данный момент я остановился только на проверке того, были ли вообще обновлены атрибуты, что кажется довольно безопасным. - person clem; 16.11.2011

Я считаю, что вам нужно определить свои ожидания, прежде чем отправлять запрос; в противном случае, к тому времени, когда он достигнет вашего ожидания, объект уже будет установлен. Так что переместите do_update после строки should_receive:

it 'should update a given vendor' do
  @vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
  do_update
end
person Dylan Markow    schedule 16.11.2011
comment
Пробовал, но по-прежнему возникает та же проблема. - person clem; 16.11.2011

Вы можете использовать метод Hash stringify keys в рельсах:

Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor).stringify_keys)
person Mark Cheverton    schedule 02.08.2012