How to Make Unit Tests for ViewModel Easier to Write and Maintain

Help yourself and others with clean code

Paul Karpukhin
Better Programming

--

Photo by Tony Pepe on Unsplash

I’m a big fan of TDD. But my TDD used to finish as soon as I started to write code for ViewModel. I used to suffer from messes my unit tests became. The more logic I added to ViewModel, the more messes I produced in unit tests.

Eventually, I used to end up deleting tests for ViewModel because it was too difficult to maintain and write a new one. But without tests I used to suffer from bugs that I (or another developer) had introduced.

So why is it difficult to write unit tests for ViewModel?

  • ViewModel of a complex screen may consist of ten or more dependencies.
  • Every class that ViewModel depends on have one or more public methods that ViewModel uses.
  • Some public methods may return different results that change ViewModel’s behaviour.
  • Sometimes, we have to verify a sequence of calls of a class or classes. For example, it can be states of views, one view is shown and another is hidden. Also, we might have to verify that one method of a class has been called, but another hasn’t.
  • It would be great to reuse code in unit tests.

So let me narrow down the list of all of the difficulties to the following questions:

  • How to handle the complexity of state verifying of ViewModel?
  • How to handle the complexity of the instantiation of ViewModel?
  • How to handle the complexity of interactions with ViewModels?

But before I start answering those questions, I’d like to say a couple of things:

  • This article is about how to organize tests for ViewModel to help you easily maintain and write new ones.
  • I’m going to keep examples as simple as possible. Solutions might look like overengineering, but they shine in a real project. At the end of the article, I will show snippets of tests for the project I’m working on.

Let’s begin.

Example

Let’s consider the simple load/content/error case:

  • The loader state is shown when data is being fetched.
  • The content state is shown if data has been loaded successfully.
  • The error state is shown if data has been loaded with an error.
  • Data is loaded and shown when a user clicks on the retry button.

Let’s also write unit tests like this one shown below:

Problem 1: How to Handle the Complexity of State Verifying of ViewModel?

Possible solution: I find it helpful to use Verifier. Verifier is the utility class that contains verifying logic.

So, after refactoring SomeViewModelTest, it looks like that:

Here are some benefits of Verifier:

  • It increases the readability of unit tests.
  • It reduces code duplication.
  • Android Studio can give hints about what can be verified. So it is harder to miss something when you write a new test.
Especially useful for new tests or refactoring the old ones

Problem 2: How to Handle the Complexity of ViewModel Instantiation in Unit Tests?

Possible solution: I find it helpful to use ViewModelBuilder. ViewModelBuilder is a utility class that is responsible for configuring ViewModel to fulfil our needs.

Mocking logic has been moved to the ViewModelBuilder. It’s important to give descriptive names for each method. So you can spend less mental efforts reading the body of a test function.

Let’s refactor SomeViewModelTest:

Here are the benefits of ViewModelBuilder:

  • Mocking logic can be reused in different tests.
  • Increase the readability of unit tests. Instantiation of view model doesn’t produce messes.
  • Android Studio can give hints about what can be mocked.
Especially useful for new tests or refactoring the old ones

Problem 3: How to Handle Complexity of Interactions With ViewModel in Unit Tests?

Possible solution: I find it helpful to use the Cases class. The Cases class is a utility class that encapsulates interaction logic with ViewModel. So it’s responsible for:

  • Mocking dependencies after ViewModel’s instantiation.
  • Encapsulation of interaction logic with ViewModel (e.g. calling public methods for clicks or public methods that Fragment or Activity calls, etc).

Is necessary to have the Case class?

I believe it is. Interactions with ViewModel could be quite complex. It’s often needed to call the same public methods of ViewModel in a specific order over and over again for new tests. There could also be hot observables that emit events at a random or particular time that changes ViewModel’s behaviour.

Example

Let’s consider the following example:

  1. ViewModel’s data is loaded with an error.
  2. A user clicks on the retry button
  3. ViewModel’s data is loaded successfully and shown.

Let’s write the Cases class:

And the test looks like this:

Benefits of having the Cases class:

  • It allows reusing interaction logic between tests.
  • Explicit naming of methods of Cases class gives an explicit idea of what is happening in a test. So it increases readability.
  • Android Studio gives hints about what case can be used.
Especially useful for new tests or refactoring the old ones

Examples From a Real Project

Compare the following two unit tests. The first one is written carelessly. The second one is written according to the approaches.

The unit test that is written carelessly
The unit test with patterns

These are only 2 of the 42 unit tests that have been written.

If unit tests are written using the first approach, it is complicated to write a new one or change the old one because of poor readability. There are also a lot of code duplications.

The second approach eliminates all of these disadvantages.

Thanks for reading!

--

--