如何在 Rails 服务中存根依赖项

How can I stub a dependency in a Rails service

我有:

用 VCR 测试库 class 效果很好,但我不确定如何测试该服务。具体来说,我如何在服务中存根 API 调用?

class GetBazsForBars
  def self.call(token)
    bazs = Foo.new(token).bars

    # do some other stuff
    bazs
  end
end

测试:

require 'rails_helper'

RSpec.describe GetBazsForBars, type: :service do
  describe ".call" do
    let(:token) { Rails.configuration.x.test_token }
    let(:bazs)  { GetBazsForBars.call(token) }

    it { expect(bazs.any?).to eq(true) }
  end
end

我不想将 Foo 传递给服务。

在 Payola 中,作者使用 StripeMock 来模拟响应。我可以写一些类似的东西,比如调用 VCR 的 FooMock 并让服务使用它吗?

不确定如何使用 Ruby 执行此操作,我习惯于将 C# / Java DI 传递给构造函数。

感谢您的帮助。

更新

移至完整答案

foo = double(bars: 'whatever')
allow(Foo).to receive(:new).with(:a_token).and_return foo

allow(Foo).to receive(:new).with(:a_token)
  .and_return double(bar: 'whatever')

@mori 提供了一个很好的起点,我最终...

1) 存根服务名称的常量:

require 'rails_helper'

RSpec.describe GetBazsForBars, type: :service do
  describe ".call" do
    before { stub_const("Foo", FakeFoo) } # <-- added this

    let(:token) { Rails.configuration.x.test_token }
    let(:bazs)  { GetBazsForBars.call(token) }

    it { expect(bazs.any?).to eq(true) }
  end
end

2) 在 /spec/fakes 中创建一个假的 foo 包装器:

class FakeFoo < Foo
  def bars
    VCR.use_cassette("foo_bars") do
      super()
    end
  end
end

这意味着在我的服务中我可以 Foo.new 并且它处理测试和现实生活一样(伟大的集成测试)。而且我没有在测试中设置任何需要实际实例才能使 VCR 调用正确的模拟链。

可能有更简单的方法,但代码对我来说看起来不错,所以发布它。