At work, I use minitest as a rails testing framework. And in the rails project, when mocking and stubing, gems such as RR and WebMock and MiniTest :: Mock, which is a standard minitest mock, are used. When writing tests, I was often confused about how to write mock and stubs. This article describes what a test double is, what is the difference between a mock and a stub, and how to use each of RR, WebMock, and MiniTest :: Mock.
-[What is test double (mock, stub)](https://qiita.com/nobu09/items/bda6205a8f170a1887d7#%E3%83%86%E3%82%B9%E3%83%88%E3%83% 80% E3% 83% 96% E3% 83% AB% E3% 83% A2% E3% 83% 83% E3% 82% AF% E3% 82% B9% E3% 82% BF% E3% 83% 96% E3% 81% A8% E3% 81% AF)
First of all, what is a test double? Also, what is the difference between mock and stub?
--A test double is a substitute for a component that the test target depends on in software testing (double means a substitute, a shadow warrior). --Mock and stub are a kind of test double
There are five variations of test doubles, according to the book xUnit Test Patterns (http://xunitpatterns.com). I've often confused stubs with mock, but ** stubs replace dependent components, and mock verifies that the output from the code under test is as expected **, I understood that.
Test stub --Used to replace the actual component on which the code under test depends --Set to return a predetermined value when calling during testing
Test spy --Capture the indirect output when the code under test is executed and save it for verification by later testing --Stub to record information based on the call
Mock object --Object used to validate indirect output from the code under test when the code under test is executed --Emphasis is placed on verification of indirect output --You can verify that the expected call was made (what argument was called, etc.)
Fake object --Objects that replace dependent components in the code under test --Implements the same functionality as the dependent component, but in a simpler way --A common reason to use fake is that the actual dependent components are not yet available, are too slow, or cannot be used in a test environment due to side effects.
Dummy object --Use a dummy object if you need an object as a parameter in the method signature of the code under test (if neither the test nor the code under test cares about this object)
reference Test Double / xUnit Patterns.com [Wiki test double](https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%B9%E3%83%88%E3%83%80%E3%83%96%E3 % 83% AB) Differences in automated test stub spy mock
RR RR is a Ruby test double framework gem. The reading is said to be'Double Ruby'. Since RR has an adapter, it seems that it can be integrated with test frameworks such as RSpec, Test :: Unit, MiniTest / MiniSpec.
GitHub:https://github.com/rr/rr Official: http://rr.github.io/rr/
RR is a test double framework that features a rich selection of double techniques and a terse syntax. RR is a test double framework that features a wealth of double techniques and concise syntax.
RR implements mock, stub, proxy, and spy. It is as the sample of RR GitHub page, but you can write it like this.
You can stub (replace the actual call) with stub
.
#Stub a method that returns nothing
stub(object).foo
stub(MyClass).foo
#A stub method that always returns a value
stub(object).foo { 'bar' }
stub(MyClass).foo { 'bar' }
#A stub method that returns a value when called with a specific argument
stub(object).foo(1, 2) { 'bar' }
stub(MyClass).foo(1, 2) { 'bar' }
See the stub page for details.
With mock
, you can create a mock that verifies that the expected call is made.
#Expect the method to be called
#Expect the foo method of object to be called
mock(object).foo
mock(MyClass).foo
#Create an expected value in the method and stub to always return the specified value
#object's foo method'bar'Expect to return
mock(object).foo { 'bar' }
mock(MyClass).foo { 'bar' }
#Create an expected value for a method with a specific argument and create a stub to return it
#object's foo method has argument 1,Called by 2'bar'Expect to return
mock(object).foo(1, 2) { 'bar' }
mock(MyClass).foo(1, 2) { 'bar' }
See the mock page for details.
It seems that you can write a spy (a stub that records the called information) by combining stub
with the description of ʻassert_received and ʻexpect (xxx) .to have_received
.
(The official GitHub had a way to write it in Test :: Unit and Rspec, but it didn't show how to write it in minitest.)
# RSpec
stub(object).foo
expect(object).to have_received.foo
# Test::Unit
stub(object).foo
assert_received(object) {|o| o.foo }
It seems that using proxy
allows you to create stubs and mock that intercept and set new return values without overriding the method completely.
#Intercept existing methods without overriding them completely
#Get a new return value from an existing value
stub.proxy(object).foo {|str| str.upcase }
stub.proxy(MyClass).foo {|str| str.upcase }
#In addition to what you're doing in the example above, create more expected value mock
mock.proxy(object).foo {|str| str.upcase }
mock.proxy(MyClass).foo {|str| str.upcase }
#Intercept a new method in the class and define a double in the return value
stub.proxy(MyClass).new {|obj| stub(obj).foo; obj }
#In addition to what we are doing in the example above.Create a mock of expected value in new
mock.proxy(MyClass).new {|obj| stub(obj).foo; obj }
For details, see mock.proxy, stub.proxy /blob/master/doc/03_api_overview.md#stubproxy) See page.
With ʻany_instance_of, you can stub or mock methods when instantiating. You can also use
stub.proxy` to access the instance itself.
#Stub methods when creating an instance of MyClass
any_instance_of(MyClass) do |klass|
stub(klass).foo { 'bar' }
end
#Another way to make the instance itself accessible
# MyClass.Stubing new instance obj
stub.proxy(MyClass).new do |obj|
stub(obj).foo { 'bar' }
end
See the #any_instance_of page for details.
If you want to use the object only for mocking, you can do it by creating an empty object.
mock(my_mock_object = Object.new).hello
You can also use mock!
as a shortcut.
#empty#Create a new mock object with a hello method and get that mock
#Mock object#Can be obtained with the subject method
my_mock_object = mock!.hello.subject
#dont_allow
#dont_allow
is the opposite of # mock
and sets the expectation that doubles will never be called. If the double is actually called, you will get a TimesCalledError
.
dont_allow(User).find('42')
User.find('42') # raises a TimesCalledError
It seems that RR uses # method_missing
to set the expected value of the method. This eliminates the need to use the #should_receive
and #expects
methods.
Also, it seems that there is no need to use the # with
method to set the expected value of the argument. (You can use it if you want)
mock(my_object).hello('bob', 'jane')
mock(my_object).hello.with('bob', 'jane') #With is attached, but the same as above
RR supports using blocks to set the return value. (You can use # returns
if you like)
mock(my_object).hello('bob', 'jane') { 'Hello Bob and Jane' }
mock(my_object).hello('bob', 'jane').returns('Hello Bob and Jane') #Same as above with returns
You can adjust the expected number of mock calls with the #times
, # at_least
, # at_most
, and # any_times
methods. You can expect # with_any_args
to allow calls with any arguments, # with_no_args
to expect calls without arguments, and # never
to expect no method to be called.
See the API overview (https://github.com/rr/rr/blob/master/doc/03_api_overview.md) for more information.
WebMock WebMock is a gem for setting HTTP request stubs and mock in Ruby. Is the difference from RR the part that specializes in HTTP requests?
GitHub:https://github.com/bblimke/webmock
Library for stubbing and setting expectations on HTTP requests in Ruby.
The following are provided as Functions.
--Stubing HTTP requests at the lib level of low-level http clients (no test changes required when changing HTTP libraries) --Expected value setting and validation for HTTP requests --Matching requests based on method, URI, header, body --Smart matching of the same URI in different representations (encoded and unencoded) --Smart matching of the same header in different expressions --Support for Test :: Unit, RSpec, minitest
Here's an excerpt from the sample WebMock GitHub Page.
You can stub a request with stub_request
.
stub_request(:any, "www.example.com") #Stub (use any)
Net::HTTP.get("www.example.com", "/") # ===> Success
#stub
stub_request(:post, "www.example.com").
with(body: "abc", headers: { 'Content-Length' => 3 })
uri = URI.parse("http://www.example.com/")
req = Net::HTTP::Post.new(uri.path)
req['Content-Length'] = 3
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req, "abc")
end # ===> Success
You can match the request body with a hash when the body is URL-Encode, JSON, or XML.
#stub
stub_request(:post, "www.example.com").
with(body: {data: {a: '1', b: 'five'}})
RestClient.post('www.example.com', "data[a]=1&data[b]=five",
content_type: 'application/x-www-form-urlencoded') # ===> Success
RestClient.post('www.example.com', '{"data":{"a":"1","b":"five"}}',
content_type: 'application/json') # ===> Success
RestClient.post('www.example.com', '<data a="1" b="five" />',
content_type: 'application/xml') # ===> Success
You can use hash_including
to match a partial hash with a request body.
#body hash_Match with partial hash including
#Can be collated even if all bodies do not match
stub_request(:post, "www.example.com").
with(body: hash_including({data: {a: '1', b: 'five'}}))
RestClient.post('www.example.com', "data[a]=1&data[b]=five&x=1",
:content_type => 'application/x-www-form-urlencoded') # ===> Success
You can match query parameters with hashes.
#stub
stub_request(:get, "www.example.com").with(query: {"a" => ["b", "c"]})
RestClient.get("http://www.example.com/?a[]=b&a[]=c") # ===> Success
As with the body, hash_including
can be used to match partial hashes and query parameters.
stub_request(:get, "www.example.com").
with(query: hash_including({"a" => ["b", "c"]}))
RestClient.get("http://www.example.com/?a[]=b&a[]=c&x=1") # ===> Success
You can use hash_excluding
to match a state that is not included in the query parameter.
stub_request(:get, "www.example.com").
with(query: hash_excluding({"a" => "b"}))
RestClient.get("http://www.example.com/?a=b") # ===> Failure
RestClient.get("http://www.example.com/?a=c") # ===> Success
You can set a stub that returns a custom response with to_return
.
#stub
stub_request(:any, "www.example.com").
to_return(body: "abc", status: 200,
headers: { 'Content-Length' => 3 })
Net::HTTP.get("www.example.com", '/') # ===> "abc"
#Raise the exception declared in the class
stub_request(:any, 'www.example.net').to_raise(StandardError)
RestClient.post('www.example.net', 'abc') # ===> StandardError
#Raise of exception instance
stub_request(:any, 'www.example.net').to_raise(StandardError.new("some error"))
#Raise an exception with an exception message
stub_request(:any, 'www.example.net').to_raise("some error")
You can also raise Timeout Exceptions (https://github.com/bblimke/webmock#raising-timeout-errors) with to_timeout
.
stub_request(:any, 'www.example.net').to_timeout
RestClient.post('www.example.net', 'abc') # ===> RestClient::RequestTimeout
When the request is repeated, it can return several different responses.
Also, connect to_return
, to_raise
, and to_timeout
with then
to return multiple responses -to_raise-or-to_timeout-declarations), [use times
to specify the number of times to return a response](https://github.com/bblimke/webmock#specifying-number-of-times-given-response- should-be-returned) You can also.
stub_request(:get, "www.example.com").
to_return({body: "abc"}, {body: "def"})
Net::HTTP.get('www.example.com', '/') # ===> "abc\n"
Net::HTTP.get('www.example.com', '/') # ===> "def\n"
#After all responses have been used, the last response is returned indefinitely
Net::HTTP.get('www.example.com', '/') # ===> "def\n"
You can allow requests to the actual network with WebMock.allow_net_connect!
. It can also be disabled with WebMock.disable_net_connect!
.
You can also Allow specific requests while disabling external requests I will.
#Allow requests to the actual network
WebMock.allow_net_connect!
stub_request(:any, "www.example.com").to_return(body: "abc")
Net::HTTP.get('www.example.com', '/') # ===> "abc"
Net::HTTP.get('www.something.com', '/') # ===> /.+Something.+/
#Disable requests to the actual network
WebMock.disable_net_connect!
Net::HTTP.get('www.something.com', '/') # ===> Failure
There are many other ways you can stub. See the Stubbing page for sample code for other uses.
WebMock's GitHub page has Test :: Unit and [How to set expected value in RSpec](https: // There was a description of github.com/bblimke/webmock#setting-expectations-in-rspec-on-webmock-module), but there was no description about minitest. It seems that minitest can be written in the same way as Test :: Unit ([Reference](https://stackoverflow.com/questions/37263232/how-to-test-external-request-with-ministest-testcase-out-] of-controller-scope)).
Use ʻassert_requested and ʻassert_not_requested
.
require 'webmock/test_unit'
stub_request(:any, "www.example.com")
uri = URI.parse('http://www.example.com/')
req = Net::HTTP::Post.new(uri.path)
req['Content-Length'] = 3
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req, 'abc')
end
assert_requested :post, "http://www.example.com",
headers: {'Content-Length' => 3}, body: "abc",
times: 1 # ===> Success
assert_not_requested :get, "http://www.something.com" # ===> Success
assert_requested(:post, "http://www.example.com",
times: 1) { |req| req.body == "abc" }
To set the expected value using a stub, write as follows.
stub_get = stub_request(:get, "www.example.com")
stub_post = stub_request(:post, "www.example.com")
Net::HTTP.get('www.example.com', '/')
assert_requested(stub_get)
assert_not_requested(stub_post)
Rspec
Write a combination of ʻexpect and
have_requested`.
require 'webmock/rspec'
expect(WebMock).to have_requested(:get, "www.example.com").
with(body: "abc", headers: {'Content-Length' => 3}).twice
expect(WebMock).not_to have_requested(:get, "www.something.com")
expect(WebMock).to have_requested(:post, "www.example.com").
with { |req| req.body == "abc" }
# Note that the block with `do ... end` instead of curly brackets won't work!
# Why? See this comment https://github.com/bblimke/webmock/issues/174#issuecomment-34908908
expect(WebMock).to have_requested(:get, "www.example.com").
with(query: {"a" => ["b", "c"]})
expect(WebMock).to have_requested(:get, "www.example.com").
with(query: hash_including({"a" => ["b", "c"]}))
expect(WebMock).to have_requested(:get, "www.example.com").
with(body: {"a" => ["b", "c"]},
headers: {'Content-Type' => 'application/json'})
You can also write the following by combining ʻa_request and
have_been_made`.
expect(a_request(:post, "www.example.com").
with(body: "abc", headers: {'Content-Length' => 3})).
to have_been_made.once
expect(a_request(:post, "www.something.com")).to have_been_made.times(3)
expect(a_request(:post, "www.something.com")).to have_been_made.at_least_once
expect(a_request(:post, "www.something.com")).
to have_been_made.at_least_times(3)
expect(a_request(:post, "www.something.com")).to have_been_made.at_most_twice
expect(a_request(:post, "www.something.com")).to have_been_made.at_most_times(3)
expect(a_request(:any, "www.example.com")).not_to have_been_made
expect(a_request(:post, "www.example.com").with { |req| req.body == "abc" }).
to have_been_made
expect(a_request(:get, "www.example.com").with(query: {"a" => ["b", "c"]})).
to have_been_made
expect(a_request(:get, "www.example.com").
with(query: hash_including({"a" => ["b", "c"]}))).to have_been_made
expect(a_request(:post, "www.example.com").
with(body: {"a" => ["b", "c"]},
headers: {'Content-Type' => 'application/json'})).to have_been_made
To set the expected value using a stub, write as follows.
stub = stub_request(:get, "www.example.com")
# ... make requests ...
expect(stub).to have_been_requested
See the Expected Value Settings (https://github.com/bblimke/webmock#setting-expectations) page for more information.
Reset all current stubs and request history with WebMock.reset!
, or WebMock.reset_executed_requests" !
can only reset the counter for requests made.
At WebMock.disable!
AndWebMock.enable!
You can disable or enable WebMock, or enable only some http client adapters.
For other features, see WebMock GitHub Page Sample Code (https://github.com/bblimke/webmock#examples).
MiniTest::Mock Finally, MiniTest :: Mock is a framework for mock objects included in minitest.
Official documentation: http://docs.seattlerb.org/minitest/Minitest/Mock.html
A simple and clean mock object framework. All mock objects are an instance of Mock. (A simple and clean mock object framework. All mock objects are instances of MiniTest :: Mock.)
Stubing an object stub
is an object extension of Minitest :: Mock.
Stubs are only valid inside the block, and the stubs are cleaned up at the end of the block. Also, the method name must exist before it can be stubed.
The stub_any_instance
method allows you to create method stubs on an instance of a class. You can use it by installing the gem of minitest-stub_any_instance_of.
--stub: Stub the method of the object --stub_any_instance_of: Stub the instance method of the class
This is a sample code of stub
.
require 'minitest/autorun'
#The class to stub
class Hello
def say
'Hello!'
end
end
hello = Hello.new
#The say method of the hello object'Hello, this is from stub!'Stub to return
hello.stub(:say, 'Hello, this is from stub!') do
hello.say #==> "Hello, this is from stub!"
end
#Stubs are disabled when you exit the block
hello.say #==> "Hello!"
Using stub_any_instance
, you can write an instance method stub as follows: It seems that this is more useful when writing instance method stubs.
require 'minitest/autorun'
require 'minitest/stub_any_instance' # minitest-stub_any_instance_of gem is also needed
#The class to stub
class Hello
def say
'Hello!'
end
end
#The say method of any instance of the Hello class'Hello, this is from stub!'Stub to return
Hello.stub_any_instance(:say, 'Hello, this is from stub!') do
Hello.new.say #==> "Hello, this is from stub!"
end
#Stubs are disabled when you exit the block
Hello.new.say #==> "Hello!"
`expect(name, retval, args = [], &blk) Expects the method name to be called, optionally with arguments (args) or blocks (blk), and a return value (retval).
require 'minitest/autorun'
@mock.expect(:meaning_of_life, 42)
@mock.meaning_of_life # => 42
@mock.expect(:do_something_with, true, [some_obj, true])
@mock.do_something_with(some_obj, true) # => true
@mock.expect(:do_something_else, true) do |a1, a2|
a1 == "buggs" && a2 == :bunny
end
The arguments are compared to the expected arguments using the'===' operator, which reduces the specific expected value. (Compare by is it included?)
require 'minitest/autorun'
# users_any_Returns true if the string method is contained in a String
@mock.expect(:uses_any_string, true, [String])
@mock.uses_any_string("foo") # => true
@mock.verify # =>true (becomes true because the mock was called as expected)
@mock.expect(:uses_one_string, true, ["foo"])
@mock.uses_one_string("bar") # =>raises MockExpectationError (because the mock wasn't called as expected)
If the method is called multiple times, specify a new expected value for each. These are used in the order they are defined.
require 'minitest/autorun'
@mock.expect(:ordinal_increment, 'first')
@mock.expect(:ordinal_increment, 'second')
@mock.ordinal_increment # => 'first'
@mock.ordinal_increment # => 'second'
@mock.ordinal_increment # => raises MockExpectationError "No more expects available for :ordinal_increment"
Make sure all methods are called as expected. Returns true
if called as expected. Raises a MockExpectationError
if the mock object is not called as expected.
See the MiniTest :: Mock page for more information.
For both RR and WebMock, the official documentation has a good sample of how to use it, so it seems good to read it. Since the amount of information in MiniTest :: Mock was small, I thought it would be easier to imagine if I checked the movement of mock
and stub
with ʻirband
rails c. (At runtime,
require'minitest / autorun'` is required.)
RR / GitHub RR page WebMock / GitHub MiniTest::Mock MiniTest stub minitest-stub_any_instance Mock, Stub Study Group (ruby) Differences in automated test stub spy mock Test Double / xUnit Patterns.com Using stub and mock with minitest [Wiki test double](https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%B9%E3%83%88%E3%83%80%E3%83%96%E3 % 83% AB)
xUnit Test Pattern Looking at the variations of the test double, here is [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.co.jp/xUnit-Test-Patterns-Refactoring-Addison-Wesley-ebook/dp/ B004X1D36K) books have appeared frequently. It seems that only the English version has been published, but I was able to confirm the content on the Web (in English). http://xunitpatterns.com