How I Learned to Love Unit Testing with Toothpick

Siqi Guo
Groupon Product and Engineering
5 min readOct 13, 2017

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 of DealActivity. It is responsible for reacting to user interaction and updating the view. DealPresenter has four dependencies, DealUtil, DealApiClient, WishlistManager and DealViewStateModel.
  • 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:

  1. ToothPickRule creates dealPresenterUnderTest.
  2. It finds four fields annotated with @Inject in DealPresenter: DealUtil, DealApiClient ,WishlistManager and DealViewStateModel.
  3. It tries to inject DealUtil into dealPresenterUnderTest and finds that we have defined a mock for this type with MockitoRule, so it will inject the mock.
  4. Same with DealApiClient and WishlistManager.
  5. While injecting DealViewStateModel, as we haven’t defined any mock for that type, it will inject a real one.
  6. 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.

--

--