首页 > 解决方案 > RSpec 是否允许我使用匹配器选择模拟?

问题描述

我有很多电话

Kernel.open(url).read

在一些遗留代码中,我试图在大规模重构之前模拟特征测试。

我不喜欢参数被忽略的方式

allow_any_instance_of(Kernel).to(receive_message_chain(:open, :read)
    .and_return(return_value))

所以我把它换成了

def stub_kernel_open_read(args: '[arg1, arg2, k: v, etc]', returning:)
  allow_any_instance_of(Kernel).to(receive(:open).with(*args)).and_return(double.tap { |d|
    allow(d).to(receive(:read)).and_return(returning)
  })
end

但我发现我遇到了这些错误:

       "http://my-fake-server/whatever" received :open with unexpected arguments
     expected: ("http://my-fake-server/whatever", {:http_basic_authentication=>["asdf", "asdf"]})
          got: ({:http_basic_authentication=>["asdf", "asdf"]})
   Diff:
   @@ -1,3 +1,2 @@
   -["http://my-fake-server/whatever",
   - {:http_basic_authentication=>["asdf", "asdf"]}]
   +[{:http_basic_authentication=>["asdf", "asdf"]}]

    Please stub a default value first if message might be received with other args as well. 

所以我发现如果我将我的存根扩展到这个:

allow_any_instance_of(Kernel).to(receive(:open).with(*args)) { |instance|
  return double.tap { |d|
    allow(d).to(receive(:read)) {
      return returning
    }
  }
}

然后instance具有 URL 的值。就目前而言,这很好,我可以建立一个允许的 URL 列表,但感觉很糟糕。

有没有类似的东西

allow_any_instance_of(Kernel).that(eq('http://whatever')).to(receive(:open))

还是我只是在完全错误的胡同里吠叫?

显然,我可以使用全局搜索替换来包装Kernel.open(url).read代码,并正确地模拟该全局,但如果可能的话,我宁愿避免这种情况。

标签: rubyrspec

解决方案


AFAIU 你的问题是正确的,你需要这样的东西,告诉模拟行为“正常”:

allow(Kernel).to receive(:open).with(url).and_return(stub)
allow(Kernel).to receive(:open).with(anything).and_call_original # I can't check now, but there's a chance this one should go first, but I doubt it

那么就

allow(stub).to receive(:read).and_return('something')

如果你必须模拟Kernel.open更多的 URL,它会变得有点乱,但原理是一样的

allow(Kernel).to receive(:open).with(first_url).and_return(first_stub)
allow(Kernel).to receive(:open).with(second_url).and_return(second_stub)
allow(Kernel).to receive(:open).with(anything).and_call_original # I can't check now, but there's a chance this one should go first


allow(first_stub).to receive(:read).and_return('something')
allow(second_stub).to receive(:read).and_return('something else')

除非我完全错过了你问题的重点?


推荐阅读