How I Learned to Love Unit Testing with Toothpick
There are tons of reasons why we should write unit tests. Unit tests help you find bugs in your code and improve your code design as well. You can find many good threads explaining why unit tests are worth your time. But in this blog post, rather than discussing why unit tests are important, I want to introduce a new Dependency Injection library called Toothpick, which can make unit testing simpler.
Mocking
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:
DealActivity
is used to display the details of a deal.DealPresenter
is the presenter ofDealActivity
. It is responsible for reacting to user interaction and updating the view.DealPresenter
has four dependencies,DealUtil
,DealApiClient
,WishlistManager
andDealViewStateModel
.
DealUtil
is an utility class for deals.DealApiClient
is used to make network calls.WishlistManager
is used to add or remove the deal from the wishlist.DealViewStateModel
contains 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 DealUtil
, DealApiClient
and 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 DealUtil
, DealApiClient
and 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 DealUtil,
DealApiClient
and WishlistManager
, when testing DealPresenter
. Note that we do not inject it but instantiate it manually, hence there is no need to mock the DealViewStateModel
.
Toothpick
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!
ToothPickRule
Toothpick supports Mockito and EasyMock test frameworks. We will use Mockito in our example. Mockito comes with a JUnit Rule, MockitoRule
. 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:
First, the ToothPickRule
will create a real instance of the class under test.
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:
ToothPickRule
createsdealPresenterUnderTest
.- It finds four fields annotated with
@Inject
in DealPresenter:DealUtil
,DealApiClient
,WishlistManager
andDealViewStateModel
. - It tries to inject
DealUtil
intodealPresenterUnderTest
and finds that we have defined a mock for this type withMockitoRule
, so it will inject the mock. - Same with
DealApiClient
andWishlistManager
. - 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!
Conclusion
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.