用 Minitest 惯用地模拟 OpenURI.open_uri
Idiomatically mock OpenURI.open_uri with Minitest
我有调用 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
调用成功。我可以把它存根,但我希望有一种方法可以让调用链更接近开始。
对于这个问题,测试间谍可能是解决方法:
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.
对于Minitest
我们可以使用gemspy.
在我们的测试环境中installing, and including之后,测试可以重排如下:
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:
class UnderTest
def get_request(uri)
open(uri).read
end
end
事实上,"production" 环境中的 open
可以 return Tempfile
的实例,quacks 方法 read
。然而,在您的 "test" 环境中,当 "stubbing" 时,您不需要提供 "real" 类型 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
模块。
此外,我们定义了return通过使用以下代码的方法调用编辑的内容:
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", but asserting if spy
registered call with "http://yahoo.com”参数调用了 get_request
。
这证明我们的 spy
按预期工作。
答案很长,但我尽力提供了最好的解释,但我不希望所有事情都清楚 - 如果您有任何问题,我非常乐意提供帮助,并更新答案因此!
祝你好运!
我对类似问题的解决方案:
URI::HTTPS.any_instance.stubs(:open).returns(file)
有了新的 Rspec,您可以简单地使用:
allow(URI).to receive(:open).and_return(your_value)
在你的代码中做
URI.open(link)
因为不建议直接在内核上调用 open。
我有调用 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
调用成功。我可以把它存根,但我希望有一种方法可以让调用链更接近开始。
对于这个问题,测试间谍可能是解决方法:
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.
对于Minitest
我们可以使用gemspy.
在我们的测试环境中installing, and including之后,测试可以重排如下:
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:
class UnderTest
def get_request(uri)
open(uri).read
end
end
事实上,"production" 环境中的 open
可以 return Tempfile
的实例,quacks 方法 read
。然而,在您的 "test" 环境中,当 "stubbing" 时,您不需要提供 "real" 类型 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
模块。
此外,我们定义了return通过使用以下代码的方法调用编辑的内容:
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", but asserting if spy
registered call with "http://yahoo.com”参数调用了 get_request
。
这证明我们的 spy
按预期工作。
答案很长,但我尽力提供了最好的解释,但我不希望所有事情都清楚 - 如果您有任何问题,我非常乐意提供帮助,并更新答案因此!
祝你好运!
我对类似问题的解决方案:
URI::HTTPS.any_instance.stubs(:open).returns(file)
有了新的 Rspec,您可以简单地使用:
allow(URI).to receive(:open).and_return(your_value)
在你的代码中做
URI.open(link)
因为不建议直接在内核上调用 open。