【问题标题】:What is the most modern way of stubbing/mocking a method in a Rails controller using RSpec?使用 RSpec 在 Rails 控制器中存根/模拟方法的最现代方法是什么?
【发布时间】:2018-12-19 00:25:14
【问题描述】:

我一直在使用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 中,不清楚特定实例是否应该接收 name 两次,或者是否预期两个接收总数。 (是前者。)
  • 使用此功能通常是一种设计味道。可能是您的测试试图做的太多,或者被测对象太复杂。
  • 它是 rspec-mocks 中最复杂的功能,历史上收到的错误报告最多。 (核心团队中没有人积极使用它,这无济于事。)

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

我认为以下几点不重要,但以防万一:

  • 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”的现代方法是什么。

【问题讨论】:

  • 关于您对第一个答案的评论(联系其他服务):您是否考虑过使用 vcr/webmock 制作(最初)、保存、然后伪造/重播响应?您可以提出一次请求,修改 yml 文件(如果需要对其进行通用化,删除秘密或其他内容),它将重播,而无需将来与远程建立任何连接
  • 如果你能提供一个具体的例子,我可以告诉你我将如何测试它。
  • @JayDorsey 我没有看过 vcr/webmock。我会看看那些。从你的描述来看,这听起来很有希望。然而,虽然这可能会解决我现在关心的问题,但我是在更一般的背景下提出这个问题的。
  • @aridlehoover 希望格式正确显示:模块 A def my_pain 将“我不想在测试中运行它” end end class TestController
  • @aridlehoover 显然我试图在 cmets 中放置一个代码示例并没有成功。我用一个例子编辑了原始帖子。

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


【解决方案1】:

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

【讨论】:

  • 当我说控制器方法时,我真正想模拟的方法是从混入到与其他服务联系的控制器中的助手。这些方法检索用于渲染、计算等的数据,因此我需要它们在测试期间返回一些假数据。
【解决方案2】:

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

  • 在测试变慢或需要外部资源进行配置之前,什么都不做。
    然后只模拟速度慢或访问外部的依赖项/类 资源(网络服务、文件系统等)

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

如果您决定模拟一些依赖项:
如果提供给被测类的依赖实例

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

【讨论】:

  • OP 要求用现代方式模拟控制器规范中的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多