Unit Testing With Dagger 2: Brewing a Potion With Fakes, Mocks, and Custom Rules in Android

Prokash Sarkar
The Startup
Published in
5 min readFeb 9, 2021

--

Photo by Jan Ranft on Unsplash

If you have Dagger in your project but never used it for testing, it’s high time to give it a try. Dagger makes your code so much easier to test. Once the test setup is complete, all you have to do is plug and play with the dependencies. Also, dependency injection is a crucial concept in testing, and you should use it whenever possible.

I highly recommend having hands-on experience in Dagger and Unit testing before going through the article. It’s required to have the fundamental knowledge of Dagger to understand how things work under the hood.

Exploring the Test Application

Overview

The sample app has only one screen that searches through Github repo by a keyword. The app fetches the API response through a Data Repository and outputs the result inside a ViewModel. Finally, an Activity watches for LiveDatachanges and sets the output inside a TextView.

Dagger Configuration

The test project has five application-level modules and one module for each Activity.

The functionality of each module:

ActivityBuildersModule

This module works as an entry point for all of the Activities. Whenever there’s a new Activity in the app, we can add it to the module with a @ContributesAndroidInjector annotation. Inside the annotation, we can pass the modules for the given Activity, e.g., Fragment, ViewModel. It will create a sub-component for the modules and add it as a child to the Activity.

AppModule

It returns app-specific dependencies created with a @Provides annotation.

AppModuleBinds

It returns app-specific dependencies created with a @Binds annotation.

NetworkModule

It returns everything related to the Networking stuff. E.g., Retrofit, OkHttp etc.

AppComponent

AppComponent hosts all modules and facilitates the Dagger dependency tree.

BaseApplication

Finally, We have our BaseApplication class to initialize the Dagger process.

Test Goals

For the sake of simplicity, let’s test the DefaultDataRepositoryfrom the data package. It has only one method, getSearchResponse() to search through Github for repositories by a keyword. Let’s create our test class and try to prepare an instance of the DefaultDataRepository. Note that,DefaultDataRepository constructor requires a DataSource as an argument. First, we will check the traditional approach of providing dependency and switch to a Dagger dependency later.

Here, we have a mock of the DataSource. Whenever we call the getSearchResponse() from DataSource, the mock will return a fake response. Our test needs to verify if the dataRepository.getSearchResponse() actually trigged the remoteDataSource.getSearchResponse() function with proper arguments.

Notes:

  1. The test function is wrapped inside a runBlockingTest. It’s part of the kotlinx.coroutines.testlibrary and helps running any suspend function inside a test.
  2. Since we are using MockK, coEvery and coVerifyare specially designed to handle any suspend function.

Run the test, and it should succeed. So far, everything looks good.

Blending Dagger for the Unit Test

We already created a successful Unit test by using mocks. Our effort to create Dagger dependencies will never pay off if we can’t use them in the Unit test. Our goal is to access the following classes in Unit tests and use them as dependencies.

  1. Modules (To provide all of our dependencies)
  2. AppComponent (To host all modules and create the dependency tree)
  3. BaseApplication (To trigger the Dagger injection)

We will take one step at a time to make the solution.

TestAppModule

We are only interested in AppModule. Since we have to change the behavior of the RemoteDataSource() function only. Let’s create a TestAppModule that provides the same type of DataSource() instance but a mock one.

TestAppComponent

We can use the same AppComponent from our app, but we might need to exclude or create a mock version of the Unit test modules. It would be wise not to mess up with the real AppComponent. We will make a TestAppComponent that extends from AppComponent but has some Unit test specific changes. Also, we need to create a function for passing an instance of the Test class. Dagger needs this to enable field injection for the Test class.

TestBaseApplication

By default, BaseApplication will launch whenever we run our app on the device. Let’s not mess up with our real BaseApplication class and create a test class that extends it. This way, we will have a separate but the same type of Application class for the app.

But how can we load up the TestBaseApplication in our test?

Introducing the Test Runner

We can use a Unit test runner to load our Application class whenever the Unit test runs. We will create a new Rule class and extend it from the AndroidJUnitRunner.

Once the Test Runner is ready, let’s plug it into the build.gradle file.

Wrapping up the test class

We are close to the Dagger setup. Let’s prepare the Dagger injection inside the @Before method so that before each test, a new configuration will be ready. This way, changes in dependencies from one test won’t affect the logic in other Unit tests.

Now, we can update the test dependencies with @Inject annotation and re-run our test.

Abracadabra, We spelled our first magic with the potion.

Source Code

The source code for the article is available on Github.

Reference

--

--

Prokash Sarkar
The Startup

An Audiophile and Android enthusiast. Currently pursuing a perfect blend of style and function for a wide range of Android Applications.