使用Minitest模仿OpenURI.open_uri [英] Idiomatically mock OpenURI.open_uri with Minitest

查看:72
本文介绍了使用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调用成功.我可以解决这个问题,但我希望有一种方法可以使呼叫链更接近起点.

解决方案

对于此问题,可以使用测试间谍:

测试间谍是一个函数,它记录参数,返回值,此值以及所有调用所引发的异常(如果有). 测试间谍可以是匿名函数,也可以包装现有函数.

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

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

安装

(1):由于Ruby的 鸭子键入 ,您实际上不需要在测试中提供将在应用程序的非测试运行中创建的确切对象.

让我们看看您的UnderTest类:

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

实际上,在生产"环境中的open可能返回Tempfile的实例,该实例使用方法read出错.但是,在您的测试"环境中,存根" 时,您无需提供类型为Tempfile的真实"对象.足以提供任何,使令人毛骨悚然一样.

在这里,我使用了 OpenStruct 的功能,以构建某物,该内容将响应read消息.让我们仔细看看:

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

在我们的测试案例中,我们提供了 minimum (最小)数量的代码,以使测试通过.我们不在乎其他 Tempfile 方法,因为我们的测试仅依赖read.

(2):我们在open方法中创建 spy Kernel模块,这可能会造成混淆,因为我们需要OpenURI模块.当我们尝试时:

Spy.on_instance_method(OpenURI, :open)

它抛出异常,即

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

轮到将open方法附加到提到的Kernel模块.

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

and_return { OpenStruct.new(read: mock_json) }

执行测试脚本时,将执行@test_under.get_request(test_uri),该注册将open方法调用及其自变量注册到我们的spy对象上.我们可以通过(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可以正常工作.

答案很长,但是我试图提供尽可能最好的解释,但是我不希望所有事情都一目了然-如果您有任何疑问,我很乐意为您提供帮助,并相应地更新答案!

祝你好运!

I have code that invokes OpenURI.open_uri and I want to confirm the URI being used in the call (so a stub isn't going to work for me), but also intercept the call. I'm hoping not to have to abstract away the call to OpenURI.open_uri just for test purposes. What I've come up with seems verbose and overly complicated.

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

Am I misusing or misunderstanding Minitest's mocking?

Part of the dancing around is that I'm also creating a Tempfile so that my read call succeeds. I could stub that out but I'm hoping there's a way I could head off the call chain closer to the beginning.

解决方案

For this problem, test-spies could be the way to go:

A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. A test spy can be an anonymous function or it can wrap an existing function.

taken from: http://sinonjs.org/docs/

For Minitest we can use gem spy.

After installing, and including it in our test environment, the test can be rearranged as follows:

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): Because of duck typing nature of Ruby, you don't really need to provide in your tests exact objects that would be created in non-test run of your application.

Let's take a look at your UnderTest class:

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

In fact, open in "production" environment could return instance of Tempfile, which quacks with method read. However in your "test" environment, when "stubbing", you don't need to provide "real" object of type Tempfile. It is enough, to provide anything, that quacks like one.

Here I used the power of OpenStruct, to build something, that will respond to read message. Let's take a look at it closer:

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

In our test case we're providing the minimal amount of code, to make the test pass. We don't care about other Tempfile methods, because our tests rely only on read.

(2): We're creating a spy on open method in Kernel module, which might be confusing, because we're requiring OpenURI module. When we try:

Spy.on_instance_method(OpenURI, :open)

it throws exception, that the

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

It turns that the open method is attached to mentioned Kernel module.

Additionally, we define what will be returned by method call with following code:

and_return { OpenStruct.new(read: mock_json) }

When our test script executes, the @test_under.get_request(test_uri) is performed, which registers the open method call with its arguments on our spy object. This is something thah we can assert by (3).

Test what can go wrong

Ok, for now we've seen that our script proceeded without any problems, but I'd like to highlight the example of how assertion on our spy could fail.

Let's modify a bit the test:

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

Which when run, will fail with something similar to:

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

We have called our get_request, with "http://google.com", but asserting if spy registered call with "http://yahoo.com" argument.

This proves our spy works as expected.

It's quite long answer, but I tried to provide the best possible explanation, however I don't expect all things are clear - if you have any questions, I'm more than happy to help, and update the answer accordingly!

Good luck!

这篇关于使用Minitest模仿OpenURI.open_uri的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆