Mocks and doubles
This is the initial pass at #6 (closed) . It contains the majority of features that RSpec has in regards to mocking.
There are two conceptual types added: mocks and doubles. A mock wraps an existing type, whereas a double is its own type. Both support stubbing methods.
There are various types of doubles. Plain doubles must be defined before they can be used. The syntax is as follows:
double :my_double do
stub my_method
end
it "returns nil" do
dbl = double(:my_double)
allow(dbl).to receive(:my_method)
expect(dbl.my_method).to be_nil
end
The double is given an identifier, which is :my_double
in this case.
The same identifier must be used when defining and instantiating a double.
Methods on doubles are defined by using stubs.
Stubs are very flexible in their syntax.
A default implementation can be specified by providing a block.
double :my_double do
stub one_arg(arg) : Int32
stub splat(*args) : Int32
stub add(left, right) { left + right }
end
it "takes an argument" do
dbl = double(:my_double)
allow(dbl).to receive(:one_arg).and_return(42)
expect(dbl.one_arg(0)).to eq(42)
end
it "takes splat arguments" do
dbl = double(:my_double)
allow(dbl).to receive(:splat).and_return(42)
expect(dbl.splat(1, 2, 3)).to eq(42)
end
it "provides a default implementation" do
dbl = double(:my_double)
# No allow() statement needed here.
expect(dbl.add(1, 2)).to eq(3)
end
Anonymous doubles can be used to quickly stub responses. Anonymous doubles aren't defined and take a string for their identifier (name).
it "is less verbose" do
dbl = double("Anonymous", foo: 42)
expect(dbl.foo).to eq(42)
end
Verifying doubles check that a type actually responds to methods that it has stubs for. The identifier for verifying doubles is the type it should check against. If the type doesn't exist yet (planned), then it behaves like a normal double.
# Method to test.
def string_length(str)
str.size
end
def string_invalid(str)
str.invalid
end
double String do
stub size : Int32
stub invalid : Int32
end
it "computes the string length" do
dbl = double(String)
allow(dbl).to receive(:size).and_return(42)
expect(string_length(dbl)).to eq(42)
end
it "is doesn't have an #invalid method" do
dbl = double(String)
allow(dbl).to receive(:invalid).and_return(42)
string_invalid(dbl) # Raises an error because String doesn't have an #invalid method.
end
All types of doubles can become a null double.
Null doubles respond to every call.
They return self
by default.
They can be created in two ways: null_double
and double(NAME).as_null_object
.
null_double :null do
stub not_self { 42 }
end
let(dbl) { double(:null) }
it "can be stubbed" do
expect(dbl.not_self).to eq(42)
end
it "returns itself" do
expect(dbl.foo).to be(dbl)
end
double :thing do
end
it "returns itself when using .as_null_object" do
dbl = double(:thing).as_null_object
expect(dbl.foo).to be(dbl)
end
Mocks are used when the original type must be used. They have the same syntax as doubles, and must be defined before they can be used.
mock String do
stub size : Int32
end
it "overrides the real value" do
string = "foobar"
allow(string).to receive(:size).and_return(42)
expect(string.size).to eq(42)
end
The following receive()
modifiers are allowed:
and_return
and_return(value)
and_return(*values)
and_raise(type)
and_raise(exception)
and_raise(message)
and_call_original
with(*args, **opts)
Spy functionality is enabled on all mocks and doubles.
double :my_double do
stub :my_method { 42 }
end
it "calls #my_method" do
dbl = double(:my_double)
dbl.my_method
expect(dbl).to have_received(:my_method)
end
it "calls #my_method (2)" do
dbl = double(:my_double)
expect(dbl).to receive(:my_method)
dbl.my_method
end
The following receive()
expectations are allowed:
with(*args, **opts)
once
twice
exactly(count).times
at_least(count).times
at_most(count).times
at_least_once
at_least_twice
at_most_once
at_most_twice