2012-03-21 2 views
57

У меня есть контроллер импорта в рельсах, который импортирует несколько CSV-файлов с несколькими записями в мою базу данных. Я хотел бы проверить в RSpec, если записи действительно сохраняются с помощью RSpec:Как сказать «any_instance» «should_receive» любое количество раз в RSpec

<Model>.any_instance.should_receive(:save).at_least(:once) 

Однако я получаю ошибку говоря:

The message 'save' was received by <model instance> but has already been received by <another model instance> 

надуманный пример контроллера:

rows = CSV.parse(uploaded_file.tempfile, col_sep: "|") 

    ActiveRecord::Base.transaction do 
    rows.each do |row| 
    mutation = Mutation.new 
    row.each_with_index do |value, index| 
     Mutation.send("#{attribute_order[index]}=", value) 
    end 
    mutation.save   
end 

Можно ли проверить это с помощью RSpec или есть ли какое-либо обходное решение?

+0

Какая версия RSpec вы используете и какое сообщение об ошибке вы видите? –

+0

rspec (2.8.0), и сообщение: сообщение «сохранить» было получено <экземпляром модели>, но уже получено <другим экземпляром модели> –

+0

. Это ожидаемое поведение. Точка any_instance заключается в том, чтобы не знать, какой единственный экземпляр ожидает что-то, но он все еще ограничивает его одним экземпляром. –

ответ

39

Там новый синтаксис для этого:

expect_any_instance_of(Model).to receive(:save).at_least(:once) 
+3

Дизайнеры rspec препятствуют использованию 'expect_any_instance_of'. Вот [ссылка на документ] (https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance). –

+0

@TylerCollier да, я полностью согласен. Из этого документа: «Использование этой функции часто является запахом дизайна. Возможно, ваш тест пытается сделать слишком много или что тестируемый объект слишком сложный». – muirbot

+8

Это на самом деле не работает для меня; такой же ошибка как оригинальный вопрос. с использованием RSpec 3.3.3. – steakchaser

15

я, наконец, удалось сделать тест, который работает для меня:

mutation = FactoryGirl.build(:mutation) 
    Mutation.stub(:new).and_return(mutation) 
    mutation.should_receive(:save).at_least(:once) 

Метод окурок возвращает один единственный экземпляр, который принимает метод сохранить несколько раз. Поскольку это единственный экземпляр, я могу отказаться от метода any_instance и использовать метод at_least.

+3

yuk. слишком плохо вы не можете сказать «many_instances». – Rob

42

Вот лучший ответ, который избавляет от необходимости переопределить: новый метод:

save_count = 0 
<Model>.any_instance.stub(:save) do |arg| 
    # The evaluation context is the rspec group instance, 
    # arg are the arguments to the function. I can't see a 
    # way to get the actual <Model> instance :(
    save_count+=1 
end 
.... run the test here ... 
save_count.should > 0 

Кажется, что метод заглушки может быть присоединен к любому экземпляру без ограничения, и сделать блок может сделать подсчет что вы можете проверить, чтобы он назывался правильным числом раз.

Update - новая RSpec версия требует этот синтаксис:

save_count = 0 
allow_any_instance_of(Model).to receive(:save) do |arg| 
    # The evaluation context is the rspec group instance, 
    # arg are the arguments to the function. I can't see a 
    # way to get the actual <Model> instance :(
    save_count+=1 
end 
.... run the test here ... 
save_count.should > 0 
+0

А славное использование заглушки на any_instance! Выглядит лучше и динамичнее, чем мое решение. –

+1

Несмотря на то, что ваш менее хакерский, мне не нравятся счетчики, путем проксирования метода класса [здесь] [http://stackoverflow.com/a/15038406/619510] Я сохранил тот же синтаксис. – michelpm

+0

Блестящий! Возьмите +1 за это. : D –

10

Stub как этот

User.stub(:save) # Could be any class method in any class 
User.any_instance.stub(:save) { |*args| User.save(*args) } 

Тогда ожидать, как это:

# User.any_instance.should_receive(:save).at_least(:once) 
User.should_receive(:save).at_least(:once) 

Это упрощение this gist, чтобы используйте any_instance, так как вам не нужен прокси-сервер для исходного метода. См. Этот принцип для других целей.

+1

Отлично. Проксирование метода до известного экземпляра отлично работает. Вы можете так же легко заглушить (: сохранить) на тестовом двойнике вместо класса User. –

+0

Работает как очарование! Благодаря! :-) – wrzasa

+0

Просто используйте 'allow' и' allow_any_instance_of' вместо 'stub' и' any_instance.stub', поскольку прежние теперь устарели (см. Https://github.com/rspec/rspec-mocks#settings-mocks -or-stubs-on-any-instance-of-a-class) – wrzasa

2

Моего случай был немного по-другому, но я оказался в этом вопросе к фигурному уронить мой ответ здесь. В моем случае я хотел заглушить любой экземпляр данного класса. Я получил ту же ошибку, когда использовал expect_any_instance_of(Model).to. Когда я изменил его на allow_any_instance_of(Model).to, моя проблема была решена.

Заканчивать документацию для некоторых более фона: https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

5

Это пример Робы с помощью RSpec 3.3, который больше не поддерживает Foo.any_instance. Я нашел это полезным, когда в объектах, создающих петлю

# code (simplified version) 
array_of_hashes.each { |hash| Model.new(hash).write! } 

# spec 
it "calls write! for each instance of Model" do 
    call_count = 0 
    allow_any_instance_of(Model).to receive(:write!) { call_count += 1 } 

    response.process # run the test 
    expect(call_count).to eq(2) 
end 
+0

Спасибо! здорово! –

Смежные вопросы