【问题标题】:RSpec: stub methods in iteratorRSpec:迭代器中的存根方法
【发布时间】:2021-04-25 14:50:53
【问题描述】:

我想学习如何使用存根。

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 新的交付方法的正确方法是什么,这些方法是从 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

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

【问题讨论】:

    标签: ruby-on-rails ruby rspec stub


    【解决方案1】:

    这里有一些东西。

    首先是您的控制器状态消息是错误的。您现在所做的是返回 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
    

    【讨论】:

    • @TDD 感谢您抽出宝贵时间!如果这不是控制器,而只是某个类,我需要测试这个类的方法。并且任务将学习使用存根。
    【解决方案2】:

    在您当前的实现中有几件奇怪的事情。首先,这个:

    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 上运行端到端场景,并且您正在建立真正的数据库对象,那么您可以确保以更全面/更真实的方式测试所有内容。

    【讨论】:

    • 谢谢!正如我上面写的,这不是工作控制器代码。这只是一个我想学习如何使用存根的例子。当一种方法使用另一种方法时。可能没有必要发布这个问题(
    • "this is not working controller code" -- 好的,在这种情况下,我会收回关于初始化程序很奇怪的部分,或者需要将它写成要求规格。但我仍然支持 cmets 为 current_usersubscribers 分配合理的值(即 UserUsers 的数组,而不是字符串)。
    • 此外,您可能会使用have_been_enqueued matcher 来简化一些关于DailyEmail 的存根。
    • 如果您的问题与现实不符,很难给出具体建议,但希望我上面写的内容足够有帮助。
    猜你喜欢
    • 2013-07-28
    • 2016-03-03
    • 1970-01-01
    • 1970-01-01
    • 2012-10-24
    • 2014-09-23
    • 2013-08-22
    • 1970-01-01
    • 2013-03-01
    相关资源
    最近更新 更多