How I Learned to Love Unit Testing with Toothpick
Android Software Engineer
October 13, 2017
Once upon a time, you started a new app and everything was simple and nice: a few features, a simple UI and that’s it. But then it became bigger and bigger, and the logic became more complex, more entangled. Suddenly, you have a database, you have multiple network calls, several components talking to each others on different threads, callbacks everywhere and multiple user interactions.
The state of your app is modified from everywhere and at any time. At this point you can’t even clearly say in which state your app is after a user interacts with a few elements in the UI.
In object-oriented programming, a class usually depends on other classes. However, as a good practice, you should only test one method of one class at a time. If a test interacts with more than one class, it would not be a unit test, but an integration test instead. In that case, when the test fails, it’s not clear which unit caused the failure. It could be the class under test or one of its dependencies.
Mock objects are commonly used to isolate the class under test from its dependencies. They are created to mimic the behavior of real objects. We can use mocked versions of the dependencies instead of real ones when unit testing. The class under test is unaware of whether the dependencies are mock or real objects. So we can test that the class under test behaves as expected by controlling the state of the mocks.
Here is an app, inspired from our business domain at Groupon, that sells deals. It uses the MVP pattern:
DealActivityis used to display the details of a deal.
DealPresenteris the presenter of
DealActivity. It is responsible for reacting to user interaction and updating the view. DealPresenter has four dependencies,
DealUtilis an utility class for deals.
DealApiClientis used to make network calls.
WishlistManageris used to add or remove the deal from the wishlist.
DealViewStateModelcontains no logic. It is simply a bundle of variables that represent the view state. There is no method in that class, so nothing to mock.
DealPresenter needs to be isolated from its dependencies, when we are testing it. We can do this by creating mock objects for
WishlistManager. Then, expose these mocks to
DealPresenter with Dependency Injection.
Dependency Injection (DI)
The intent behind DI is to decouple the dependencies from a class by passing in instances of those dependencies.
In the example above, instances of
WishlistManager are not created by
DealPresenter, but are passed into its constructor.
How can DI make mocking easy? The class under test is not in charge of instantiating it’s dependencies. They will be passed to the object by the DI and the object just has to use them. Consequently, when unit testing, we can pass in mock objects which have the same interface as the real dependency.
In the previous example, we can pass in mocks for
WishlistManager, when testing
DealPresenter. Note that we do not inject it but instantiate it manually, hence there is no need to mock the
DI seems simple to implement in the example above, but if there are more classes in project, it’s not so simple anymore. In the graph below, class A has dependencies B and C, and class C has dependencies D and E. If we want to create these classes using DI, we need to inject D and E into C, then we need to inject B, C, D and E into A. Try to imagine in a real Android app, the dependency tree may be much bigger, it is a disaster to implement the DI manually. 😱
So we usually use dependency injection frameworks. You may have heard of some popular ones such as Dagger or Roboguice. At Groupon, we actively contribute to many open source libraries and have also developed a DI library named Toothpick. It’s as fast as Dagger, but designed with ease-of-use in mind. If you are interested in the library, you can get more info from Github.
Creating dependencies is really easy with Toothpick, we just need to annotate the fields with
@Inject. Toothpick will create the dependencies and assign them to the right field for us:
But let’s go back to our topic, why am I talking about Toothpick? Because it comes with advanced test support!
Toothpick supports Mockito and EasyMock test frameworks. We will use Mockito in our example. Mockito comes with a JUnit Rule,
MockitoRule injects mocks into the fields that are annotated with
@Mock, that way we can use them both in our test and in the class under test at the same time.
Afterwards, we can include the
ToothPickRule provided by Toothpick. It does two things:
ToothPickRule will create a real instance of the class under test.
Smart people doing interesting work
Second, it will inject the dependencies of the class under test. If we have created a mock using the
MockitoRule, it will inject that mock inside the class under test. Otherwise, it will create a real object and inject it.
So for our example these would be the steps:
- It finds four fields annotated with
- It tries to inject
dealPresenterUnderTestand finds that we have defined a mock for this type with
MockitoRule, so it will inject the mock.
- Same with
- While injecting
DealViewStateModel, as we haven’t defined any mock for that type, it will inject a real one.
- We have our class isolated and ready to be unit tested!
Although there are some alternative DI libs available for Android and Java, Toothpick is still worth checking out. It is effortless to use and provides powerful test support. Thanks to the ToothPickRule, we can inject the mocks inside the class that we are testing in an simpler manner.
Unit testing helps improve your code quality and it becomes more and more important as you scale your projects. With Toothpick, you will start to enjoy writing unit tests, so I recommend you to go try it out!
With 💚 , the Groupon Android team.
Thanks to daniel hw, Jaden Choi, Eric Farraro, Aolei Zhang, Stephane NICOLAS, David Luu, Weihua Wang, Alin Turcu, and Michael Ma.