Test Doubles
Goal
- Learn the different types of test doubles.
- Understand how test doubles can be used to test units with dependencies.
- Get experience using a popular mocking framework for unit testing.
Concepts
- brittle
- dummy object
- fake object
- mock object
- Mockito
- spy object
- stub
- test double
Library
org.mockito.Mockito
org.mockito.Mockito.doReturn(Object toBeReturned)
org.mockito.Mockito.mock(Class<T> classToMock)
org.mockito.Mockito.when(T methodCall)
org.mockito.stubbing.OngoingStubbing.thenReturn(T value)
org.mockito.stubbing.OngoingStubbing.thenThrow(Throwable... throwables)
org.mockito.stubbing.Stubber
org.mockito.stubbing.Stubber.when(T mock)
Dependencies
Lesson
Your first unit tests were easy, because the units you were testing were simple. The classes under scrutiny were small and self-contained, with no dependencies. But as applications gain functionality, the classes that perform the business logic have to specialize and collaborate: they develop dependencies.
Unit tests should be quick. They should focus on small, self-contained units of functionality. If an implementation has dependencies, this presents a complication. What if its dependency needs to access a database, or download a file over the Internet, for example? This would not only slow down the unit test, it would introduce other points of failure, such as the Internet connection going down.
Test Doubles
To keep a unit test fast and focused, even when the unit being tested has dependencies, you can to create substitutes that will take the place of the dependencies, just long enough for the tests to run. These substitutes are known as test doubles, and they come in various types. Like stunt doubles in a movie, they play the role of the real actor only in certain circumstances; in production the application will use the real implementations.
Here are some reasons you might want to use a test double for a dependency:
- The real dependency implementation takes a while to run.
- The real dependency implementation depends on resources that might not be available at test time.
- The real dependency implementation might be complicated, introducing extra unknowns or variables to test.
- The real dependency might come with additional dependencies, and so on several layers deep.
Dummy
A dummy object is a double used in testing that doesn't need to actually do anything. Let's go back to our example of vehicles and racetracks, and imagine that there is a RaceManager
interface for business logic that will determine the prize money to be given when a vehicle wins first, second, or third place.
We want to test RaceManagerImpl
to ensure that a car coming in at first place will receive $1000 in prize money. But to test these methods, we need an instance of the RaceCar
interface.
Do we have to create a full-fledged implementation of RaceCar
just to see if the prize money is awarded correctly? If you have one handy, of course, you can use it, but it may come with its own dependencies. If you don't expect the RaceManagerImpl
to care about the car make and model, you can just pass in a dummy object.
Stub
A test stub is slightly smarter than a dummy in that it provides real answers. But this sort of test double only provides pre-programmed answers—it has no real intelligence to provide answers based upon input data or otherwise make decisions. It just provides canned responses.
Maybe we are required to set aside some of the winnings for tax purposes. To find out the tax rate, RaceManagerImpl
must query the TaxStrategy
interface, which unfortunately is full of methods we're not interested in for this particular test.
Rather than implementing TaxStrategy
in its entirety, we could simply stub out
an implementation to return a tax rate we expect, and base our test assertions with that taken into consideration.
Mock
Sometimes the predetermined values of a stub are not enough to test a particular unit. You might want the dependency to return values based upon what is passed to it. This type of test double is called a mock object.
You might have wondered earlier how RaceManagerImpl
knew how much money to dole out for prizes in the first place. It may be that RaceManagerImpl
actually queries a PrizeStrategy
interface. The implementation may look like this:
The PrizeStrategy
implementation we use must be a little smarter than a stub, because it must return a prize amount based upon which place the driver came in. We must add a little logic to the mock implementation.
Spy
A spy object is one that makes notes about how it was called. Any test double could function as a spy in addition to its normal functionality. Most often, the spying consists of counting how many times a method was called.
A unit test might thus want to verify that when RaceManagerImpl.getPrize(…)
is called, the implementation uses the PrizeStrategy
by making a single call to PrizeStrategy.getPrize(…)
.
Fake
A fake object is a full implementation, but it may take shortcuts for testing or otherwise not be robust enough for production use. The archetypal example is an in-memory repository. Suppose that a class has a dependency on a RaceCarRepository
that stores the make, model, and other information about all the race cars ever used. There may be a situation in which the class we're testing has several interactions with the repository, so we can't simply create a mock with a couple of methods.
We happen to have handy an implementation RemoteDatabaseRaceCarRepository
, which connects to a relational database in another country. But this would not be good to use in unit tests. It wouldn't be speedy, and the remote connection might not even be reliable while tests are running. Moreover unless we had a firm control over the remote database, we wouldn't even know what values it would give us back.
Rather than using a real implementation that actually contains a history of wins of various drivers, we can create a complete implementation of RaceCarRepository
that allowed storing and retrieving race cars. But because we would only use this in tests, there would be no need to actually store and retrieve the objects from permanent storage—an in-memory collection would suffice.
Mockito
Creating a new test double by hand for each unit test is cumbersome and repetitive. That's why several libraries have been created to ease the process, making the use of mocks almost transparent. One of the most popular libraries to use with JUnit is Mockito.
The class org.mockito.Mockito
provides most of the static methods you'll use to get started. Going behind the scenes to find out how Mockito works isn't really necessary at this point.
It is sufficient to know how you can use Mockito in practice. Mockito comes with many options and variations in approach, but here are the basic steps:
- Tell Mockito to create a mock using
Mockito.mock(…)
. - Use
Mockito.when(…)
withOngoingStubbing.thenReturn(…)
orOngoingStubbing.thenThrow(…)
to define results to mock methods. - Use the mock as a dependency to the class you are testing, making JUnit assertions afterwards as normal.
- (optional) Use
Mockito.verify(…)
or one of its variations to ensure that the mock was called
Mockito Dummy
Once you create a test double using Mockito.mock(…)
, the resulting instance can function as a test dummy with no further work—the object will return reasonable default values for all methods. We can thus recreating our example above. These examples assume you have statically imported the methods of the main Mockito class as explained above.
Mockito Stub
To specify what a Mockito stub returns, you'll simply need to add a Mockito.when(…)
followed by a OngoingStubbing.thenReturn(…)
specifying the value you'd like back. Here's how it would work in the example above:
If we wanted to verify that that RaceManagerImpl
called TaxStrategy.getTaxRate()
, we could use Mockito.verify(…)
like this:
This would throw an exception if the TaxStrategy.getTaxRate()
method was not called once and only once. There are other variations of this method that allow you to check for multiple calls.
Mockito Mock
Creating an actual mock with Mockito requires only the additional step of specifying the appropriate method argument when you perform the method call for Mockito.when(…)
. Mockito will perform the magic to remember the argument(s) you used, so that the return value you provide for OngoingStubbing.thenReturn(…)
will be returned only for the given input value(s) you gave.
Review
Gotchas
- These test doubles are only for testing, not for production! Put them in the
test
source code tree, away from production code.
In the Real World
- Like dependency injection, the use of mocks can be overused, obscuring what (if anything) is actually being tested.
- Dummy, stub, and mock test doubles have the the potential of making your tests brittle. Use complete fake implementations if at all possible.
- If your mocking tests get too abstract, you may find that your tests aren't really testing anything relevant to the real world. Make sure you aren't testing tautologies.
Think About It
- Don't make a test double just for the sake of creating a test double. Use an existing implementation if it is small, simple, and quick.
Self Evaluation
- Why might one want to use a test double instead of a real implementation when testing units with dependencies?
- What is the difference between a stub and a mock?
- Why would using a fake dependency result in tests that are less brittle than using mocks and stubs?
Task
- Add a unit test to for
BookerServicesImpl
.- Use Mockito to mock the
PublicationManager
dependency.
- Use Mockito to mock the
- Add a unit test for
PublicationManagerImpl
.- Create a fake
PublicationRepository
without using Mockito to use as the dependency.
- Create a fake
See Also
- Mocks Aren't Stubs (Martin Fowler)
- Does stubbing make your tests brittle? (szeryf)
- Unit tests with Mockito (Vogella)