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

Prokash Sarkar
Feb 9 · 5 min read
Image for post
Image for post
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

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.

Image for post
Image for post

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

Image for post
Image for post

The functionality of each module:

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.

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

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

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

AppComponent hosts all modules and facilitates the Dagger dependency tree.

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.

Image for post
Image for post

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.

Image for post
Image for post

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.

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.

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?

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.

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.

Image for post
Image for post

Abracadabra, We spelled our first magic with the potion.

Source Code

The source code for the article is available on Github.

Reference

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Prokash Sarkar

Written by

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

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Prokash Sarkar

Written by

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

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store