【问题标题】:Idiomatically mock OpenURI.open_uri with Minitest习惯性地用 Minitest 模拟 OpenURI.open_uri
【发布时间】:2015-03-02 15:09:21
【问题描述】:

我有调用 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 调用成功。我可以解决这个问题,但我希望有一种方法可以让调用链更接近开始。

【问题讨论】:

    标签: ruby mocking minitest


    【解决方案1】:

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

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

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

    对于Minitest,我们可以使用gem spy

    在我们的测试环境中installingincluding之后,测试可以重新安排如下:

    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):由于duck typing 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"
    

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

    (2):我们在Kernel 模块中的open 方法上创建spy,这可能会造成混淆,因为我们需要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) 被执行,它在我们的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,但断言如果spy 使用“http://yahoo.com”参数注册了调用。

    这证明我们的spy 可以正常工作。

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

    祝你好运!

    【讨论】:

    • Dziękuję bardzo/Cheers @Pawel Dawczak 给出了彻底的答案——这也消除了我对 OpenURI.open_uriKernel.open 之间存根/嘲笑的一些困惑。
    • Cała przyjemność po mojej stronie/我的荣幸 :)。是的,当需要open-uri 丰富了Kernel.open 方法并具有处理请求的能力时,可能会更容易理解。如果您在不需要open-uri 的情况下执行open("http://google.com"),它将以Errno::ENOENT: No such file or directory @ rb_sysopen - http://google.com 结束,但在需要之后,同样的事情会执行预期的=&gt; #&lt;Tempfile:/var/folders/z0/kdcygzhj0cz40vvymknl52q80000gn/T/open-uri20150305-17560-1azpjax&gt;。是的 - 它接受 url 作为字符串,不需要用 URI 包装
    【解决方案2】:

    我对类似问题的解决方案:

    URI::HTTPS.any_instance.stubs(:open).returns(file)
    

    【讨论】:

      【解决方案3】:

      使用新的 Rspec,您可以简单地使用:

      allow(URI).to receive(:open).and_return(your_value)
      

      在你的代码中做

      URI.open(link)
      

      因为不推荐直接在 Kernel 上调用 open。

      【讨论】:

        猜你喜欢
        • 2016-04-26
        • 1970-01-01
        • 1970-01-01
        • 2017-02-14
        • 2012-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多