用Minitest惯性模拟OpenURI.open_uri

我有调用OpenURI.open_uri代码,我想确认调用中使用的URI(因此存根不会对我有用),但也会拦截调用。 我希望不必为了测试目的而抽象地调用OpenURI.open_uri 。 我想出的东西似乎冗长而过于复杂。

under_test.rb

 require 'open-uri' class UnderTest def get_request(uri) open(uri).read end end 

test_under_test.rb

 require 'minitest/autorun' require './lib/under_test' class TestUnderTest < Mintest::Test def test_get_request @under_test = UnderTest.new mock_json = '{"json":[{"element":"value"}]}' uri = URI('https://www.example.com/api/v1.0?attr=value&format=json') tempfile = Tempfile.new('tempfile') tempfile.write(mock_json) mock_open_uri = Minitest::Mock.new mock_open_uri.expect(:call, tempfile, [uri]) OpenURI.stub :open_uri, mock_open_uri do @under_test.get_request('https://www.example.com/api/v1.0?attr=value&format=json' end mock_open_uri.verify end end 

我是否误用或误解了Minitest的嘲笑?

跳舞的部分原因是我也在创建一个Tempfile以便我的read呼叫成功。 我可以把它排除在外,但我希望有一种方法可以让我更接近开头的呼叫链。

对于这个问题,测试间谍可能是要走的路:

测试间谍是一个函数,它记录所有调用的参数,返回值,this值和抛出exception(如果有)。 测试间谍可以是匿名函数,也可以包装现有函数。

取自: http : //sinonjs.org/docs/

对于Minitest我们可以使用gem间谍 。

安装并将其包含在我们的测试环境中后,可以按如下方式重新排列测试:

 require 'minitest/autorun' require 'spy/integration' require 'ostruct' # (1) require './lib/under_test' class TestUnderTest < Minitest::Test def test_get_request mock_json = '{"json":[{"element":"value"}]}' test_uri = URI('https://www.example.com/api/v1.0?attr=value&format=json') open_spy = Spy.on_instance_method(Kernel, :open) # (2) .and_return { OpenStruct.new(read: mock_json) } # (1) @under_test = UnderTest.new assert_equal @test_under.get_request(test_uri), mock_json assert open_spy.has_been_called_with?(test_uri) # (3) end end 

(1):由于Ruby的duck typing性质,你并不需要在测试中提供在应用程序的非测试运行中创建的确切对象。

我们来看看您的UnderTest课程:

 class UnderTest def get_request(uri) open(uri).read end end 

事实上,在“生产”环境中open可能会返回Tempfile实例,它会在方法read 。 但是在“测试”环境中,当“存根”时 ,您不需要提供Tempfile类型的“真实”对象。 提供任何东西就足够了。

在这里,我使用OpenStruct的强大function来构建一些能够响应read消息的东西。 让我们仔细看看它:

 require 'ostruct' tempfile = OpenStruct.new(read: "Example output") tempfile.read # => "Example output" 

在我们的测试用例中,我们提供了少量的代码,以使测试通过。 我们不关心其他Tempfile方法,因为我们的测试只依赖于read

(2):我们在Kernel模块中的open方法上创建了一个间谍 ,这可能会OpenURI感到困惑,因为我们需要OpenURI模块。 当我们尝试:

 Spy.on_instance_method(OpenURI, :open) 

它抛出exception,即

 NameError: undefined method `open' for module `OpenURI' 

它将open方法附加到提到的Kernel模块。

另外,我们使用以下代码定义方法调用返回的内容:

 and_return { OpenStruct.new(read: mock_json) } 

当我们的测试脚本执行时,执行@test_under.get_request(test_uri) ,它在我们的spy对象上注册open方法调用及其参数 。 这是我们可以通过(3)断言的东西。

测试可能出错的地方

好吧,现在我们已经看到我们的脚本没有任何问题,但我想强调一下我们的spy断言如何失败的例子。

让我们修改一下测试:

 class TestUnderTest < Minitest::Test def test_get_request open_spy = Spy.on_instance_method(Kernel, :open) .and_return { OpenStruct.new(read: "whatever") } UnderTest.new.get_request("http://google.com") assert open_spy.has_been_called_with?("http://yahoo.com") end end 

哪个在运行时会失败,类似于:

  1) Failure: TestUnderTest#test_get_request [test/lib/test_under_test.rb:17]: Failed assertion, no message given. 

我们使用“ http://google.com ”调用了我们的get_request ,但是使用“ http://yahoo.com ”参数声明spy注册的调用。

这certificate我们的spy按预期工作。

这是一个很长的答案,但我试图提供最好的解释,但我不希望所有的事情都清楚 - 如果你有任何问题,我很乐意提供帮助,并相应地更新答案!

祝好运!