簡體   English   中英

RSpec:迭代器中的存根方法

[英]RSpec: stub methods in iterator

我想學習如何使用存根。

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

我寫了一些東西,但這個實現對我來說似乎很糟糕。 我不明白如何處理迭代器以及如何測試狀態。 請給一些提示

這里有幾件事。

首先是您的 controller 狀態消息是錯誤的。 您現在所做的是返回 201 正文,而不是狀態。 它應該返回 200,您正在執行發送電子郵件的操作,而不是創建 object。 如果您不想返回除肯定錯誤消息之外的任何其他內容並且不處理錯誤消息(您應該這樣做),您應該將201替換為: render status: 200

您的測試目前並沒有真正測試任何東西。 如果您的訂閱者方法有錯誤它不會捕獲它,如果您的郵件 class 有一個錯誤有一個錯誤您不會捕獲它,那么重點是什么。

對於 controller 本身的邏輯,您應該正確循環通過要交付的郵件,並檢查它們是否已交付https://relishapp.com/rspec/rspec-rails/docs/mailer-specs或拆分測試在兩個。 您使用 controller 測試測試快樂路徑並為 email class 創建另一個測試。

運行 controller 測試的正確方法是使用請求規范,並期望正確的響應代碼。 請注意,如果您在測試數據庫中啟用了訂閱者,它將返回 200,如果沒有訂閱者,它也只會返回 200。整個代碼仍在測試中。 如果訂閱方法中有錯誤,它將返回 500 錯誤。 https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec

要正確測試 controller 測試,您需要在測試數據庫中創建對象,然后循環遍歷它們,而不是像那樣嘗試模擬它。 例如,您可以使用 FactoryBot 執行此操作,或者您甚至可以刪除用戶 model,如果您由於某種原因無法像這樣添加 FactoryBot; 取決於您在郵件 class 中所做的事情。

 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 應用程序中測試控制器的標准方法是通過請求規范 (您可以使用controller 規格,但這通常被認為是劣等的,因為您繞過了完整堆棧的關鍵部分,例如路由器。)

其次,這些奇怪的變量:

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 上運行端到端場景,並且您正在建立真正的數據庫對象,那么您可以確保以更全面/更現實的方式測試所有內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM