如何测试从控制器

时间:2016-04-19 20:58:16

标签: ruby-on-rails rspec controller

我已将部分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控制器测试。

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

我知道在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

如果我在运行测试时在控制器中执行了nil;由于某种原因,此测试会导致控制器中的actioned_bar输出为public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); if (is_array($controller)) { $controller = reset($controller); } if ($controller instanceof IInterface) { return false; } } ,但对所有其他控制器测试都很好。

我有没有办法测试在此控制器规范中调用ActionedBar?

3 个答案:

答案 0 :(得分:1)

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

如果您想使用原始对象,可以使用expect(ActionedBar).to receive(:new).and_call_original(不会#new只返回nil,您将获得NoMethodError)。

答案 1 :(得分:0)

核心问题是您的规范中的actioned_bar不会是控制器中ActionedBar的同一个实例。因此,规范将始终失败。

相反,当调用new时,你需要让ActionedBar返回一个double:

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

然而,我通常认为这种测试有一种代码味道 - 可以嘲笑外部合作者并设定你传递正确信息的期望。但您可能想要考虑您是否正在测试控制器如何完成其​​工作的细节而不是实际行为。

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

答案 2 :(得分:0)

您可以设置由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