【问题标题】:How to test that a class is called in a controller method with RSpec如何使用 RSpec 在控制器方法中测试一个类是否被调用
【发布时间】:2019-01-20 00:39:51
【问题描述】:

我正在测试我的控制器以确保调用了一个库类并且该功能按预期工作。 注意:这可能已在其他地方问过,但我需要帮助解决我的具体问题。我也很想知道如何最好地对此进行测试。

为了更好地解释我的问题,我将通过代码提供上下文。 我的/Lib 文件夹中有一个类,它会发出事件(如果您不明白这意味着什么,请不要介意)。这个类看起来像这样:

class ChangeEmitter < Emitter
  def initialize(user, role, ...)
    @role = role
    @user = user
    ...
  end

  def emit(type)
    case type
    when CREATE
      payload = "some payload"
    when UPDATE
      payload = "some payload"
    ...
    end

    send_event(payload, current_user, ...)
  end
end

这是我在控制器中使用它的方式:

class UsersController < ApplicationController

  def create
    @user = User.new(user_params[:user])
    if @user.save
      render :json => {:success => true, ...}
    else
      render :json => {:success => false, ...}
    end
    ChangeEmitter.new(@user, @user.role, ...).emit(ENUMS::CREATE)
  end
end

对不起,如果某些代码没有意义,我试图在不暴露太多代码的情况下解释问题。

这是我为测试所做的尝试:

describe UsersController do
  before { set_up_authentication }

  describe 'POST #create' do
    it "calls the emitter" do
      user_params = FactoryGirl.attributes_for(:user)
      post :create, user: user_params

      expect(response.status).to eq(200)
      // Here is the test for the emitter
      expect(ChangeEmitter).to receive(:new)
    end
  end
end

我希望ChangeEmitter 类接收new,因为它在执行创建操作时立即被调用。

相反,这是我得到的错误:

(ChangeEmitter (class)).new(*(any args))
       expected: 1 time with any arguments
       received: 0 times with any arguments

我在上面的代码中遗漏了什么,为什么类没有收到新的。有没有更好的方法来测试上述功能?请注意,这是 Rspec。您的帮助将不胜感激。谢谢。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 rspec-rails rspec3


    【解决方案1】:

    您需要将expect(ChangeEmitter).to receive(:new) 代码放在发布请求上方。当您期望一个类接收一个方法时,您的“期望”语句会在调用控制器之前进行。它期待着未来会发生一些事情。所以你的测试应该是这样的:

    it "calls the emitter" do    
      user_params = FactoryGirl.attributes_for(:user)
    
      expect(ChangeEmitter).to receive(:new)
    
      post :create, user: user_params
    
      expect(response.status).to eq(200)
    end
    

    编辑

    在注意到您在调用“new”之后链接了“emit”操作后,我意识到我需要针对您的特定用例更新我的答案。您需要返回一个可以调用 emit 的对象(我通常返回一个间谍或双精度对象)。有关间谍和替身之间区别的更多信息,请查看: https://www.ombulabs.com/blog/rspec/ruby/spy-vs-double-vs-instance-double.html

    基本上,spy 将接受对其调用的任何方法并返回自身,而使用 double 你必须指定它可以接受哪些方法以及返回什么。对于您的情况,我认为间谍可以工作。

    所以你想这样做:

    it "calls the emitter" do    
    
      user_params = FactoryGirl.attributes_for(:user)
    
      emitter = spy(ChangeEmitter)
      expect(ChangeEmitter).to receive(:new).and_return(emitter)
    
      post :create, user: user_params
    
      expect(response.status).to eq(200)
    end
    

    【讨论】:

    • 当我这样做时,我收到一个错误,NoMethodError: undefined method `emit' for nil:NilClass。方法emit 在ChangeEmiiter 类中。
    • 啊,我没有注意到您在创建操作中调用“new”之后链接了一个方法。您将希望“期望(ChangeEmitter)。接收(:新)”返回一个对象。我通常通过首先存根将返回的对象来做到这一点。我将更新我的答案,向您展示我的意思的示例
    • 我已经更新了我的帖子 - 间谍是一种模拟对象并接受该对象上的任何方法的简单方法 - 它们随 RSpec 3 提供。
    • 如果您有兴趣,我也刚刚更新了我的答案,以讨论使用 spy 与 double 之间的区别。感谢您接受答案
    猜你喜欢
    • 2014-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多