Как проверить, вызывается ли метод для определенных объектов, извлеченных из БД в Rails с помощью RSpec?

#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)