使用RSpec在Rails控制器中存根/模拟方法的最现代方法是什么?

时间:2018-12-19 00:25:15

标签: ruby-on-rails unit-testing rspec rspec-rails

我一直在使用in this article中描述的技术作为请求规范中控制器中的模拟方法。 TLDR,该技术使用“ allow_any_instance_of”模拟控制器方法。我发现了一些其他Stackoverflow帖子,这些帖子似乎与本文一致。

但是,在阅读rspec 3.8 documentation时,它显示以下内容:

  
      
  • rspec-mocks API是为单个对象实例设计的,但是此功能可在整个对象类上运行。结果,在语义上有些混乱的情况。例如,在Expect_any_instance_of(Widget).to receive(:name).twice中,尚不清楚某个特定实例是否希望两次接收名称,或者是否希望总共接收两次。 (是前者。)
  •   
  • 使用此功能通常是一种设计气味。可能是您的测试尝试执行太多操作,或者被测对象太复杂。
  •   
  • 这是rspec-mocks的最复杂的功能,并且历来收到最多的bug报告。 (没有核心团队积极使用它,这无济于事。)
  •   

在请求规范中模拟方法的正确和“现代”方法是什么?

我认为以下内容无关紧要,以防万一:

  • Rails 5.2
  • Ruby 2.4.1
  • RSpec 3.8

编辑:添加一些示例代码。

module A
  def my_pain
    puts "I don't want to run this during testing"
  end
end

class TestController < ApplicationController
  include A
  def index
    my_pain
  end
end

那么在控制器/请求测试中使用RSpec模拟“ #my_pain”时测试“ #index”的现代方法是什么。

2 个答案:

答案 0 :(得分:-1)

我认为,我们不需要测试Controller,只需进行功能测试。 因为最终用户期望的是要呈现的内容,而不仅仅是导航。

答案 1 :(得分:-1)

另一种方法-尝试尽可能少地模拟。

  • 在测试变慢或需要外部资源进行配置之前,不要进行任何模拟。
    然后仅模拟缓慢的依赖项/类或访问外部 资源(网络服务,文件系统等)

  • 在测试设置变得非常复杂之前,不要进行任何模拟。
    然后模拟包含非常复杂的逻辑的依赖项,以简化/减少要测试的逻辑分支的数量。

如果您决定模拟一些依赖项:
如果在测试中向类提供了依赖项实例

class Order
    def initialize(printer)
        @printer = printer
    end

    def print
        @printer.print("my-printer", self)
    end
end

# Test
RSpec.describe "Order.print" do
    it "prints order to my printer" do
        fake_printer = double("Printer", :print => nil)
        order = Order.new(fake_printer)

        expect(fake_printer).to receive(:print).with("my-printer", order)

        order.print
    end
end

如果依赖项实例在测试中的类内部实例化了

class Order
    def initialize
        @printer = Printer.new("my-printer")
    end

    def print
        @printer.print(self)
    end
end

# Test
RSpec.describe "Order.print" do
    it "prints order to my printer" do
        fake_printer = double("Printer", :print => nil)
        allow(Printer).to receive(:new).with("my-printer").and_return(fake_printer)

        order = Order.new

        expect(fake_printer).to receive(:print).with(order)

        order.print
    end
end

请注意,在使用double("ClassName")时,您需要配置所有在测试过程中将被调用的方法,否则将引发异常。

RSpec Test Doubles