简体   繁体   English

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

[英]What is the most modern way of stubbing/mocking a method in a Rails controller using RSpec?

I've been using the technique described in this article for mocking methods in controllers in my request specs. 我在请求规范中一直在使用本文中描述的技术来模拟控制器中的方法。 TLDR, the technique uses "allow_any_instance_of" to mock controller methods. TLDR,该技术使用“ allow_any_instance_of”模拟控制器方法。 I've found some other StackOverflow posts which seem to agree with the article. 我发现了一些其他Stackoverflow帖子,这些帖子似乎与本文一致。

However, while reading through the rspec 3.8 documentation , it says the following: 但是,在阅读rspec 3.8文档时 ,它显示以下内容:

  • The rspec-mocks API is designed for individual object instances, but this feature operates on entire classes of objects. rspec-mocks API是为单个对象实例设计的,但是此功能可在整个对象类上运行。 As a result there are some semantically confusing edge cases. 结果,在语义上有些混乱的情况。 For example, in expect_any_instance_of(Widget).to receive(:name).twice it isn't clear whether a specific instance is expected to receive name twice, or if two receives total are expected. 例如,在Expect_any_instance_of(Widget).to receive(:name).twice中,尚不清楚某个特定实例是否希望两次接收名称,或者是否希望总共接收两次。 (It's the former.) (是前者。)
  • Using this feature is often a design smell. 使用此功能通常会带来设计异味。 It may be that your test is trying to do too much or that the object under test is too complex. 可能是您的测试尝试执行太多操作,或者被测对象太复杂。
  • It is the most complicated feature of rspec-mocks, and has historically received the most bug reports. 这是rspec-mocks最复杂的功能,并且历来收到最多的bug报告。 (None of the core team actively use it, which doesn't help.) (没有核心团队积极使用它,这无济于事。)

What is the correct and "modern" way to mock a method in a request spec? 在请求规范中模拟方法的正确和“现代”方法是什么?

I don't think the following matters, but just in case: 我认为以下内容无关紧要,以防万一:

  • Rails 5.2 Rails 5.2
  • Ruby 2.4.1 Ruby 2.4.1
  • RSpec 3.8 RSpec 3.8

Edit: Adding some example code. 编辑:添加一些示例代码。

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

So what is the modern way to test "#index" while mocking "#my_pain" with RSpec in a controller/request test. 那么在控制器/请求测试中使用RSpec模拟“ #my_pain”时测试“ #index”的现代方法是什么。

In my opinion, we don't need to test the Controller, just take feature tests. 我认为,我们不需要测试Controller,只需进行功能测试。 because what end-users are expecting is what will be rendered, not just navigation. 因为最终用户期望的是要呈现的内容,而不仅仅是导航。

Another approach - try to mock as little as possible. 另一种方法-尝试尽可能少地模拟。

  • Mock nothing until tests become slow or require external resources to configure. 在测试变慢或需要外部资源进行配置之前,不要进行任何模拟。
    Then mock only dependencies/classes which are slow or access external resources(webservice, file system etc.) 然后仅模拟慢速的依赖项/类或访问外部资源(Web服务,文件系统等)。

  • Mock nothing until test setup become very complicated. 在测试设置变得非常复杂之前,不要进行任何模拟。
    Then mock dependencies which contains very complicated logic, to simplify/reduce amount of logical branches to test. 然后模拟包含非常复杂的逻辑的依赖项,以简化/减少要测试的逻辑分支的数量。

In case you decided to mock some of dependencies: 如果您决定模拟一些依赖项:
If instance of dependency provided to the class under the test 如果依赖项实例提供给测试中的类

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

If instance of dependency instantiated inside class under the test 如果依赖项实例在测试中的类内部实例化

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

Notice that when you are using double("ClassName") you need to configure all methods which will be called during the test, if not - exception will be thrown. 请注意,当您使用double("ClassName") ,需要配置所有在测试过程中将被调用的方法,否则将抛出异常。

RSpec Test Doubles RSpec测试双打

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM