微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

RSpec:迭代器中的存根方法

如何解决RSpec:迭代器中的存根方法

我想学习如何使用存根。

class SomeClass

attr_reader :current_user

  def initialize(current_user:)
    @current_user = current_user
  end

  def deliver
    subscribers.each do |user|
      DailyEmail.new(recipient: user).deliver
    end

    201
  end

  private

  def subscribers
    User.all.select(&:email_notifications_enabled?)
  end
end

测试 DailyEmail new 的正确方法是什么,传递从 SomeClass 调用方法。如果订阅者是 activerecord 关系,我如何测试每种方法? 我如何检查迭代器后的状态返回?

我奇怪的解决方案:

RSpec.describe SomeClass do

  let(:current_user) { 'user' }
  subject { described_class.new(current_user: current_user) }

  describe '#deliver' do
    let(:subscribers) { ['test2','test1'] }

    context 'when `each`,`new`,`deliver` methods called in controller `deliver` method' do
      it 'calls methods' do
        allow(subscribers).to receive(:each)

        subscribers.each do |user|
          the_double = instance_double(DailyEmail)
          expect(DailyEmail).to receive(:new).and_return(the_double).with(recipient: user)
          expect(the_double).to receive(:deliver)
          expect(subscribers).to have_received(:each)
          subject.deliver
        end
      end
    end
  end
end

我写了一些东西,但这个实现对我来说似乎很糟糕。我不明白如何处理迭代器以及如何测试状态。请给一些提示

解决方法

这里有一些事情。

首先是您的控制器状态消息是错误的。您现在所做的是返回 201 正文,而不是状态。它应该返回 200,您正在执行发送电子邮件而不是创建对象的操作。如果您不想返回除肯定错误消息之外的任何其他内容并且不处理错误消息(您应该这样做),则应将 201 替换为:render status: 200

您的测试目前并未真正测试任何内容。如果您的订阅者方法有错误,它不会捕捉到它,如果您的邮件类有错误,您将不会捕捉到它,那有什么意义。

对于控制器本身的逻辑,您应该正确地循环传递要传递的邮件,并检查它们是否已传递 https://relishapp.com/rspec/rspec-rails/docs/mailer-specs 或将测试分成两部分。使用控制器测试测试快乐路径并为电子邮件类创建另一个测试。

运行控制器测试的正确方法是使用请求规范,并期待正确的响应代码。请注意,如果您在测试数据库中启用了订阅者,它将返回 200,如果没有订阅者,它也将只返回 200。整个代码仍在测试中。如果订阅方法中有错误,它将返回 500 错误。 https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec

要正确测试控制器测试,您需要在测试数据库中创建对象,然后循环遍历它们,而不是尝试像那样模拟它。例如,您可以使用 FactoryBot 执行此操作,或者您甚至可以删除用户模型,如果您由于某种原因无法像这样添加 FactoryBot;取决于你在邮件类中做什么。

 before :each do 
   stub_const('User',MockedUserModel)
 end
 
 class MockedUserModel < User
   def all
     arr_of_mocked_users = []
     arr_of_mocked_users << User.new(name: 'mocked_user_1',id: 1)
     arr_of_mocked_users << User.new(name: 'mocked_user_1',id: 2)
     arr_of_mocked_users 
   end

   def email_notifications_enabled?
     true
   end
 end
,

在您当前的实现中有几个奇怪的地方。首先,这个:

class SomeController < ApplicationController
  def initialize(current_user:)
    @current_user = current_user
  end
end

...定义一个自定义的 initialize 方法,纯粹是为了让测试工作??!

在现代 Rails 应用程序中测试控制器的标准方法是通过 request spec。 (您也可以使用controller specs,但是这通常被认为较差,因为您绕过了完整堆栈的关键部分,例如路由器。)

其次,这些奇怪的变量:

let(:current_user) { 'user' }
let(:subscribers) { ['test2','test1'] }

...为什么不使用真正的 User 对象??!按照自己的方式进行测试会使测试变得更加复杂、脆弱且难以推理。

有些人可能强烈希望始终将这些测试与数据库分离,在这种情况下,您可以设置模拟,例如:

allow(User).to receive(:all).and_return(subscribers)

其他人(包括我)宁愿在数据库中创建真实的对象,并接受您的测试套件在这里会慢一点。

您不需要需要在此处使用 factory_bot 之类的库,但我建议您使用它。像这样:

let(:current_user) { FactoryBot.create(:user) }
let!(:subscribers) { FactoryBot.create_list(:user,2,email_notifications_enabled: true) }
let!(:non_subscriber) { FactoryBot.create(:user,email_notifications_enabled: false) }

最后,我会在测试中去掉这些部分:

allow(subscribers).to receive(:each)
expect(subscribers).to have_received(:each)
subject.deliver

相反,如果您的测试只是在 API 上运行端到端场景,并且您正在建立真实的数据库对象,那么您可以确保以更全面/更现实的方式测试所有内容。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。