#ruby-on-rails #rspec #mocking
Вопрос:
Если у меня есть User
модель, включающая метод dangerous_action
, и где-то у меня есть код, вызывающий метод для определенного подмножества пользователей в базе данных, например:
class UserDanger
def perform_dangerous_action
User.where.not(name: "Fred").each(amp;:dangerous_action)
end
end
как я могу проверить с помощью RSpec, вызывает ли он этот метод у правильных пользователей, фактически не вызывая метод?
Я пробовал это:
it "does the dangerous thing, but not on Fred" do
allow_any_instance_of(User).to receive(:dangerous_action).and_return(nil)
u1 = FactoryBot.create(:user, name: "Jill")
u2 = FactoryBot.create(:user, name: "Fred")
UserDanger.perform_dangerous_action
expect(u1).to have_recieved(:dangerous_action)
expect(u2).not_to have_recieved(:dangerous_action)
end
но, конечно, ошибка заключается в том, что пользовательский объект не отвечает has_recieved?
, потому что это не двойной, потому что это объект, извлеченный из базы данных.
Я думаю, что мог бы сделать эту работу, исправив метод обезьяной dangerous_action
и заставив его записать в глобальную переменную, а затем проверить значение глобальной переменной в конце теста, но я думаю, что это был бы действительно уродливый способ сделать это. Есть ли какой-нибудь лучший способ?
Ответ №1:
Я понял, что на самом деле пытаюсь протестировать два аспекта perform_dangerous_action
метода. Во-первых, это область выборки базы данных, а во-вторых, она вызывает правильный метод для появляющихся пользовательских объектов.
Для тестирования области выборки БД я действительно должен просто создать область в User
классе:
scope :not_fred, -> { where.not(name: "Fred") }
который можно легко протестировать с помощью отдельного теста.
Тогда perform_dangerous_action
метод становится
def perform_dangerous_action
User.not_fred.each(amp;:dangerous_action)
end
и тест для проверки этого вызывает правильный метод для not_fred
пользователей
it "does the dangerous thing" do
user_double = instance_double(User)
expect(user_double).to receive(:dangerous_action)
allow(User).to receive(:not_fred).and_return([user_double])
UserDanger.perform_dangerous_action
end
Ответ №2:
я думаю, что во многих случаях вы не хотите разделять where
или where.not
в область, в этом случае вы можете заглушить саму связь ActiveRecord::, например:
# default call_original for all normal `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).and_call_original
# stub special `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).with(name: "...")
.and_return(user_double)
в вашем случае where.not
это на самом деле ActiveRecord::QueryMethods::WhereChain#not
метод вызова, чтобы я мог это сделать
allow_any_instance_of(ActiveRecord::QueryMethods::WhereChain)
.to receive(:not).with(name: "Fred")
.and_return(user_double)