Understanding Unit Tests for Android in 2021

Christopher Elias
ProAndroidDev
Published in
9 min readMay 19, 2021

--

Photo by Pawel Nolbert on Unsplash

Tests, unit tests, instrumented tests, medical tests, etc. Yeah yeah yeah… You heard that you need to test your code but you think that doing it is going to take more time than you expected, or probably you are thinking that testing is too hard, or the worst of all the options, you didn’t even try.

Well my friend, tests are NOT hard at all and you have to stop avoiding them. They can probably be tedious to set up (usually the instrumented ones) but hard… mmm no, I don’t think so.

Why do I even need to “test” my code? 🤔

Let’s assume we are making some “Shopping App”. In some X feature, we need to get the final product price. In order to show the user the final product price we need to make some calculations based on some inputs like the base product price & some discount that comes from somewhere (it doesn’t matter, the point is the situation).

Now that we’ve got the feature requirements, it’s time to do what we know.

  1. Start coding, get into coding flow state with your hacker sweater and your lofi music.
  2. Code finished, time to build the app and launch it in your phone.
  3. Time for manual human testing.
  4. Ops! We found a bug.
  5. Close the app, go to your price calculator class
  6. Research your code, till you finally find and “fix” the bug.
  7. Do the “fixes” and repeat the steps 2, 3… till your app is “stable”.

Repeating the same process again and again and again doesn’t seem very practical, and we could have prevent this since the beginning! Saving time that we could use doing something else like read a blog, play some game, do exercise, etc. Time is the most valuable thing we have on life, so don’t waste it.

Speaking of time, if you want to go straight to the code, checkout these classes for unit tests & these classes for instrumented test.

Unit Testing basics 🔎

What is unit test? In simple terms is the test you make to small portions of your code. Like testing the methods inside our imaginary PriceCalculator helper class.

By creating and running unit tests against your code, you can easily verify that the logic of individual units is correct.

Unit tests run on your local machine only. These tests are compiled to run locally on the Java Virtual Machine (JVM) to minimize execution time.

If your tests depend on objects in the Android framework, we recommend using Robolectric. For tests that depend on your own dependencies, use mock objects to emulate your dependencies’ behavior.

Android official docs

When we create our apps we have three directories:

main, androidTest & test directories.

Our unit tests are going to be located inside the src/test directory.

As long as your code is clean, your tests are going to be really easy to make. Let’s assume our PriceCalculator looks like this.

In order to test the logic inside finalPrice we must create a Unit Test.

Unit testing of the #finalPrice method.

In the gist from above, we are doing the following things:

  1. First we are creating a class called PriceCalculatorUnitTest inside the unit test directory.
  2. We create a new instance of our PriceCalculator class.
  3. We have only one test. With the help of the assertion methods from the junit library we can compare the expected result with the real result of our calculations!

Now it’s time to verify!

Press the run test button and that’s it. You’ve got your first unit test running and passing! You can add more tests and more assertions in order to validate your code.

At the beginning it looks like you are wasting time, but in reality you're not. You are actually winning time because you didn’t compile the whole app, go to an specific feature, do some user flow, and see the result. No, this time you test directly your code logic, and see if it fails or pass with different inputs. If you cover more code in your tests, less bugs on production you can expect.

This was very “easy” and our tests are not always going to be like that.

As long as your code is clean, your tests are going to be really easy to make

Im ready 🥋, show me a real use case sample 🔥

You learned some of the benefits of tests and the very basics of it. Let’s take our playground project from the previous posts, and analyze some of the tests.

The project is modularized by feature and uses clean architecture inside every feature module (Im going to do some blogs about modularization too, but for now you can read this great blog from Mario Sanoguera de Lorenzo, the architecture of this project is heavily inspired on his implementation).

Dependency Injection graph of the feature “Actors”

We are not going to go deep into DI in this blog. But let’s examine the structure of the graph in order to understand what we need for our Unit Tests.

  • actorsRemoteDataSource — Is the one who performs the retrofit call and returns a list of remote actors. It depends on a middleware provider, coroutine dispatcher, error class adapter & actors service. We can see that this class have a lot of dependencies that we need to supply, it is definitely not like our PriceCalculator class 😅.
  • actorsMapper — The mapper also depends on a coroutine dispatcher, a movie mapper & a resource provider! A resource provider it sounds like something that have to deal with context, but how can we access to the app context inside our Unit Tests???. Easy, we gonna mock all those classes.
  • actorsRepository — This depends on the actorsMapper & the actorsRemoteDataSource.
  • viewModel — Finally, the ViewModel just depends on the repository & the mapper.

What? You scared?… You asked for a real sample, there you go 🎁.

This feature uses MVVM, therefore it means that our ViewModel is the one who prepares the data through the repository, the result of some repository operation is going to modify our state Model and render the View according to it. If you want to know more of what Im talking about, check out this small but precise blog and come back.

Probably you have a couple of questions here. What are going to test? Who do we really need to test? I think those questions can be simplified to who prepares the data?… The repository. This class is the one who connect almost all the dots. Why almost? Because the mapper is another big player here, is the one who is going to map the actors from remote → domain → presentation.

Mocks

Mocks are just fake objects that can replace the real ones. You can create mocks manually or you can rely on libraries like mockk, mockito, etc. What should you use? I would say you can use both. It is not mandatory to use only 1 of them.

Set up our project for unit tests

Go to your build.gradle file from your app module or your android/kotlin library module and add the following dependencies to our project.

(The versions can differ according to the date where you are reading this blog).

testImplementation "io.mockk:mockk:1.11.0"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0"

Unit Testing level 2 🎯

We say that we are going to focus on two classes. The repository & the mapper. Which should go first? The easier one. Always start for the ones that are easier, it will make you advance faster.

The only dependency that can cause “troubles” 😂 is the resource provider. Let’s see it’s content.

ResourceProvider & implementation.

Context? We can’t use android context in our Unit Tests! what are gonna do now!? Lucky us or smart us, we are using interfaces. Therefore we can just use another implementation for the ResourceProvider, like this

ResourceProvider, implementation & Mocked Implementation.

Problem solved. With the help of principles like “Dependency Inversion” on our code, we depend on abstractions rather than implementations, therefore it is easy for us to solve problems like this.

One of our problems is solved. Now, let’s create our ActorsMapperUnitTest class. Remember, we are trying to test if our mapping functions work as we expected.

First part of ActorsMapperUnitTest

It wasn't so difficult at the end doesn't it? We instantiate the necessary classes for test our actors mapper class. We must be sure that all of the attributes are going to be correctly mapped from start to end. At the end, it could look like this

Portion of ActorsMappersUnitTest

It is just a plain comparison of objects after use the methods of the ActorMapper.

That’s how in 209 lines of code, we cover from START to END the actors mapper methods.

The only “challenge” here was to create a mock implementation of ResourceProvider. But you just learned how to smash that kind of obstacles.

Unit testing level 3 ☣️

We check that our mapper is working as expected 👏, one problem less to solve. But we still haven’t check the repository, let’s do it. First we need to check what objects does the repository need and create them

First part of ActorsRepositoryUnitTest

There two new things here.

  • DefaultRemoteConfig — It is just an utility class that provides some common objects, nothing special.
  • mockk<>() — Remember when I told you about the mocks object? Well this time I’m mocking the ActorService & MiddlewareProvider interfaces with the help of the mockk library. Why? Because we are going to need more flexibility for the following tests.

As we keep going forward, our tests need to be more specific. Like the one above. Could I test different repository results like success, specific failures etc with my own mocks? Yes, but probably you would end with more verbosity on your code, or worst, lose the clean code we already have. Therefore, Im supporting myself with mockk and it’s helper methods.

As you see in the image above, in a very idiomatic way, it says that every time that the middlewareProvider.getAll() gets called, it will return a list of default test middleware. Very simple.

In the other block it says that every (the “co” term is for coroutines) time that the actorsService.getActors() gets called it will throw and HttpException.

Finally, we use the runBlockingTest scope from the test coroutines package and execute our repository as we would do in the ViewModel. the getData… is just an extension function that I create for make my tests more easy.

Conclusion 🍾

Unit tests are very useful and easy to do if your code is well organized. I recommend you to use the dependency inversion principle the most of the time in order to make your code more flexible and scalable for cases like this.

You don’t have to start with all at once, go at your own time. Start with unit testing the smaller classes, the ones who have less or none dependencies, but it is important for your improvement to start from somewhere.

If you cover more code in your tests, the less you worry about app rejects.

Also, check out how to make UI tests (instrumented tests) here 👇

See you later 👋.

--

--