【问题标题】:How to test a rails PORO called from controller如何测试从控制器调用的 Rails PORO
【发布时间】:2016-04-19 20:58:16
【问题描述】:

我已将我的 Foos 控制器的一部分提取到一个新的 rails 模型中以执行该操作:

foos_controller.rb

class FoosController < ApplicationController

  respond_to :js

  def create
    @foo = current_user.do_something(@bar)
    actioned_bar = ActionedBar.new(@bar)
    actioned_bar.create
    respond_with @bar
  end

actioned_bar.rb

class ActionedBar
  def initialize(bar)
    @bar = bar
  end

  def create
    if @bar.check?
      # do something
    end
  end
end

我先让它工作,但现在我正在尝试回填 rspec 控制器测试。

我将测试各种模型方法,并将进行功能测试以确保从该角度来看没问题,但我想添加一个测试以确保从 foos 控制器调用新的 actioned_bar 模型与@bar。

我知道在 rspec 中你可以测试一些 receives 一些 with 一些 arguments 但我正在努力让它工作。

    it "calls ActionedBar.new(bar)" do
      bar = create(:bar)
      expect(ActionedBar).to receive(:new)

      xhr :post, :create, bar_id: bar.id
    end

这不起作用,控制台报告:

NoMethodError:
   undefined method `create' for nil:NilClass

这很奇怪,因为它只在我使用 expect(ActionedBar).to receive(:new) 时才这样做,其余的控制器测试工作正常。

如果我尝试这样做:

    it "calls ActionedBar.new(bar)" do
      bar = create(:bar)
      actioned_bar = ActionedBar.new(bar)
      expect(actioned_bar).to receive(:create).with(no_args)

      xhr :post, :create, bar_id: bar.id
    end

控制台说:

(#<ActionedBar:0xc8f9f74>).create(no args)
       expected: 1 time with no arguments
       received: 0 times with no arguments

如果我在运行测试时在控制器中执行put;由于某种原因,此测试导致控制器中的 actioned_bar 输出为nil,但对于所有其他控制器测试都很好。

有什么方法可以测试此控制器规范中是否调用了 ActionedBar?

【问题讨论】:

    标签: ruby-on-rails rspec controller


    【解决方案1】:

    您可以使用expect_any_instance_of(ActionedBar).to receive(:create),因为规范中的实例和控制器中的实例是不同的实例。

    如果你想使用原始对象,你可以使用expect(ActionedBar).to receive(:new).and_call_original(没有#new只会返回nil,你会得到NoMethodError)。

    【讨论】:

    • 感谢 Maxim Khan-Magomedov,这两个测试都运行良好。正如 max 在他的回答中建议的那样,我还添加了一个额外的测试,以确保实际执行操作栏。
    【解决方案2】:

    您可以设置一个由 ActionedBar.new 调用返回的双 ActionedBar,因为此实例与控制器中使用的实例不同。

    describe "#create" do
      let(:actioned_bar) { double(ActionedBar) }
      let(:bar) { double(Bar) }
    
      it "calls ActionedBar.new(bar)" do
        expect(ActionedBar).to receive(:new).with(bar).and_returns(actioned_bar)
        expect(actioned_bar).to receive(:create)
    
        xhr :post, :create, bar_id: bar.id
      end
    end
    

    【讨论】:

      【解决方案3】:

      核心问题是您的规范中的actioned_bar 与您的控制器中的ActionedBar 实例不同。因此规范总是会失败。

      相反,当调用 new 时,您需要让 ActionedBar 返回一个双精度:

      it "calls ActionedBar.new(bar)" do
        bar = create(:bar)
        actioned_bar = instance_double("ActionedBar")
        allow(ActionedBar).to receive(:new).and_return(actioned_bar)
        expect(actioned_bar).to receive(:create).with(no_args)
        xhr :post, :create, bar_id: bar.id
      end
      

      但是,我通常认为这种测试是一种代码异味 - 可以模拟外部协作者并设定您传递正确消息的期望。但是您可能需要考虑您是否正在测试控制器如何完成其​​工作的详细信息,而不是实际行为。

      我发现最好设置一个调用控制器操作的规范,并对它如何更改数据库状态或它如何影响响​​应设置期望。

      【讨论】:

      • 谢谢。我选择了 Maxim Khan-Magomedov 的回答,因为到目前为止我还不是使用双打的忠实粉丝,但我已经按照您的建议添加了一个额外的测试,以确保实际执行该操作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多