Test Doubles Defined
Learn about test doubles and their disadvantages.
One complicating factor in dealing with test doubles is that pretty much everybody who creates a tool feels perfectly free to use slightly different naming conventions than everybody else. Here are the names, which are of course also the correct ones. This naming structure is the creation of Gerard Meszaros.
Mock objects as a term
The generic term for any object used as a stand-in for another object is test double, by analogy to “stunt double” and with the same connotation of a cheaper or more specialized replacement for a more expensive real object. Colloquially, mock object is also sometimes used as a generic term but, confusingly, is also the name of a specific type of test double.
Fake objects
A fake object is typically an ordinary Ruby object that we define designed to be used only in tests. The fake object matches the API of an object but has vastly simplified or canned internals. Fakes are not typically created using a test-double library but might be custom-created for use in a specific context. A good example is the Fake Stripe gem, which creates a fake instance of the Stripe server to intercept API calls in a test situation.
Stub objects
A stub is a fake object that returns a predetermined value for a method call without calling the actual method on an actual object. A stub can be a new object created only to be a stub, which we can create in RSpec using the double
method. Alternatively, we can create a partial stub by stubbing a specific method on an existing object:
allow(thing).to receive(:name).and_return("Fred")
The line of code above says that if we call thing.name
, we’ll get Fred
as a result. Crucially, the thing.name
method is (by default) not touched, so whatever value the “real” method would return is not relevant because the Fred response comes from the stub, not the actual object. If thing.name
is not called in the test, nothing happens.
Mock objects
A mock object is similar to a stub, but in addition to returning the fake value, a mock object sets a testable expectation that the method being replaced will be called in the test. If the method is not called, the mock object triggers a test failure. We can write the following snippet to create a mocked method call instead of a stub, using expect
instead of allow
:
expect(thing).to receive(:name).and_return("Fred")
If we use the mock then call thing.name
in the test, we still get Fred
, and the thing.name
method is still untouched. But if we don’t call thing.name
in the test, the test fails with an unfulfilled-expectation error.
Stub vs. mock
In other words, setting a stub on a method is passive. It says, “Ignore the real implementation of this method and return this value.” On the other hand, setting a mock on a method is aggressive and says, “This method will return this value, and we better call the method, or else!”
Setting an expectation on whether a method is called allows us to test an object’s behavior rather than its final state. Once we’ve stubbed a method, it makes no sense to write an assertion like this one:
Get hands-on with 1400+ tech skills courses.