Writing Testable Android MVVM App: Part 5. Espresso

Brian Lee
5 min readNov 19, 2015

--

In the previous post, we explored how ViewModel’s property changes can be verified and how rotations(configuration changes) can be tested via unit tests. However, one thing that we couldn’t test with unit tests was verifying the binding of the Views. In this post, we’ll explore how we can use Espresso to verify that:

  1. The bound View is getting values from the ViewModel
  2. Interaction on the View is calling the bound method on the ViewModel

The source code for this post is tagged as part5 and available on GitHub.

Verifying Bindings

We already tested all the logic in the ViewModel in the unit tests, so we don’t want to test that again in the Espresso tests. Instead, we should focus on verifying just the bindings only. To do that, we want to be able to mock out certain parts of the ViewModel, while still allowing Android Data Binding to take care of the binding between the View and the ViewModel. So how do we do that?

Spy

Enter Spy. Mockito’s Spy makes it possible to use the real object instance, while still making it possible to mock out certain parts. Our ViewModel inherits from BaseObservable, and we want it to work like the real ViewModel, except for the parts we want to test. Spy will allows us to do exactly that.

So what do we need to do? We need to have the app use the Spy object when testing and make it available to the test, so that we can mock certain parts out and verify it. However, our current implementation won’t allow us to do that:

As seen above, the direct instantiation of the ViewModel within the Activity prevents us from using Spy and making it available to the tests.

The Plan

So how do we fix that? We’ll try tackling it as follows:

  1. Create a ViewModelFactory that will create the different ViewModels
  2. Inject the ViewModelFactory and use it to instantiate the ViewModels
  3. Provide a MockViewModelFactory with Spy by extending AppComponent and letting the test provide a different module.

The key step is to extend the AppComponent to create a test-specific AppComponent subclass, which will provide the MockViewModelFactory. @chiuki has a great post explaining this concept in her post here, and we’ll take a similar approach.

Create a ViewModelFactory

I opted to inject ViewModelFactory to create the ViewModels, instead of injecting all ViewModels via Dagger 2. I thought it might be simpler, but I’d be happy to learn about any other approaches as well!

ViewModelFactory
This will just be a simple factory interface.

AppViewModelFactory
This will be the ViewModelFactory implementation that the app will use. In addition, I’ve restricted the visibility of all ViewModel constructors to be package-private.

Inject the ViewModelFactory

Now that we have the ViewModelFactory, let’s inject it to the app! To do so, we’ll have to do some restructuring.

AppComponent
Let’s update AppComponent so it doesn’t provide the modules anymore.

SampleAppComponent
And then inherit AppComponent and provide the modules there instead.

SampleAppModule
And in here, we’ll provide the AppViewModelFactory.

MvvmSampleApplication
We can then provide the SampleAppComponent here.

ClickCountActivity
Which allows us to inject and use the ViewModelFactory.

Provide a MockViewModelFactory

Now that everything’s set up, we can provide a MockViewModelFactory with Spy that we can use for testing.

I won’t cover the details here on how I set up the TestComponent and MockModule to provide the MockViewModelFactory, as @chiuki already covered it well in her post. But please feel free to checkout the code and commits in GitHub if you’d like to find out more.

MockViewModelFactory
When working on this, I ran into a challenge. If you are familiar with Espresso, the Activity won’t be instantiated until ActivityTestRule.launchActivity() is called. So until this is called, the ViewModel won’t be instantiated. However, after it is called, the ViewModel will already be instantiated with nothing mocked, and the Activity will already be using it!

My solution was to have a SpyInitializer. By having a SpyInitializer interface, it allowed me to define the mocks before the ViewModel is instantiated. We’ll see how it gets used in ClickCountActivityTest. The code below is a simplified version just showing the ClickCountViewModel.

ClickCountActivityTest
As you can see below, having the SpyInitializer allows us to stub out methods and set the values we want, so that we can verify the bindings.

In testInitialScreen(), because we can define what gets returned when getClickCountText() gets called, we can just check that it matches with the text we defined. This is what allows us to verify just the bindings, without having to test the logic we already tested in the unit tests. All we are checking here is that the View with id R.id.clickCountText is bound to the getClickCountText() method, which is exactly what we want to do.

Likewise, in testClickButton(), all we are testing is that clicking on the View with id R.id.clickButton is bound to the onClickButton() method of the ViewModel. We don’t really care if it increments a value or if it updates a View, since we’ve already tested that in the unit test.

One Catch

If you try to run Espresso tests with Android Data Binding, you’ll run into the following error:

java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation

The issue is reported here at AOSP. One workaround, as described in this StackOverflow post, is to disable Dalvik verifications flag on the emulator:

adb shell setprop dalvik.vm.dexopt-flags v=n,o=v
adb shell stop installd
adb shell start installd

Mission Accomplished?

On the very first post, I said I started this project because:

I wanted to explore an Android app that:
1. Has (almost) no business logic in the Activity / Fragment.
2. Has fully unit testable ViewModels, including rotation (sorry Espresso!).
3. Uses Android Data Binding.
4. Has minimum Espresso testing, solely to verify the bindings.

In this 5-part series, I definitely learned a lot about how I can incorporate Android Data Binding, unit tests, and Espresso to write a fully testable Android MVVM app. As such, I am very happy with what I explored.

--

--