Espresso UI Testing With Android Architecture Components
Disclaimer
This post is targeted at people who are already familiar with:
- Kotlin
- Automated UI Testing in Espresso
- Android Architecture Components (LiveData, ViewModel)
- Mockito/Mocking Frameworks
- Dagger 2
Introduction
If you’re familiar with Espresso testing, you know it’s straightforward — just make view actions, and then view assertions to verify your UI state. However, you quickly realize the complexity that comes along with setting up a solid testing strategy.
For example when building an app, much of the time you’re dealing with an API call and displaying data from it to the UI. In the context of an Espresso test, do you want to be making an actual API call during each test to populate the UI? Probably not. Networks can be flakey and inconsistent, so instead maybe you setup a mock web server to reliably deliver this API data in each test.
This way you can preset specific API responses for each endpoint, likely read in from a JSON file containing the response. Now in your Espresso test when your networking layer calls out to the API, the mock web server responds instead with the preset data. This way you have complete control over what’s delivered in each API response and, more importantly, you’re not making real network calls in each test.
Using a mock web server for UI tests isn’t necessarily bad, but behind the scenes you’re still running most, if not all of your networking/business logic just for a UI test. I would argue that verifying all the networking/business logic should be handled by unit tests, so it doesn’t make much sense to run code in my UI tests that should be tested elsewhere. Also, having to manage several different JSON files for each mock API response is a pain if the response schema changes. Not ideal.
Another problem: If I have UI-specific scenarios to test that rely on formatting responses a certain way, does that mean I’d have to have multiple API responses for the same endpoint just for different scenarios? Probably, which isn’t good either.
This posed scenario is obviously specific to the complications of using a mock web server for Espresso tests, but I think it illustrates an important point: You really don’t need to (and shouldn’t in my opinion) run your entire app just to test your UI. Put another way: you don’t need to, and shouldn’t, run code that isn’t being tested in a test. This way you’re running code for exactly what you’re trying to test. It’s my belief that the purpose of a unit test is to help guard your code from breaking changes in the future. It’s a guarantee that the way your app is running is “correct” moving forward based on the tests you’ve written.
In a similar vein, your UI tests aren’t necessarily unit tests, but we can try to break them down to test against as much of the core code you’ve written just for the UI. That’s what this article is about and what I’ll go into detail on how I did it.
Sample App
Before we move on, let me pose a sample scenario:
We’ve built a simple app that displays data from an API call in a RecyclerView in a Fragment. When the app first loads, it kicks off a network call and displays a loading indicator while it waits for a response. Once the response is successfully delivered, the RecyclerView is populated with API data. When one of the RecyclerView rows is clicked, a toast is shown.
Here’s what that looks like:
And here’s the architecture of the app:
View (Fragment) → ViewModel → Repository → Data Source
And here’s the relevant code for testing:
Sample App Test Scenarios
Here’s a rough list of what I would say we should cover in the UI tests in our sample scenario:
- Verify the loading indicator is shown when isLoadingVisible() returns View.VISIBLE
- Verify the RecyclerView content is visible when isContentVisible returns View.VISIBLE
- Verify when a row item is clicked, plantItemClicked() is called once
I emphasize UI because that’s exactly what we want to test, nothing more. None of the above test cases require us to supply data to our repository from a mock web server or anything of that nature, so no need for it here.
In the following section, I’ll dive into this approach for automated UI tests in the sample scenario.
UI Test Implementation
Let’s think about unit tests again for a second.
In a typical unit test for a function in the ViewModel (the one above), how would we tackle it? Well, we’d mock out the necessary dependencies and stub out calls from those dependencies that return data. Then we’d verify that the logic of the ViewModel works as expected. We have a similar scenario here, except instead of stubbing out calls to the repository, we want to stub out calls from the ViewModel.
So: We want to test our Fragment’s UI and to do this we’ll need to mock the ViewModel, stubbing out the stuff our UI needs.
We’ll start off by setting up our instrumented test:
Most of it is straightforward, except this line:
private val plantListFragment = TestPlantListFragment()
Remember when we’re injecting the ViewModelFactory in our Fragment here?
fun initializeViewModelFactory() {
viewModelFactory = Injector.get().viewModelFactory()
}
Well, we don’t want to be doing that in our test. With this setup, we can’t control what the ViewModelFactory generates. What we’d ideally want is a way to set the ViewModelFactory for the Fragment so that when called upon, it generates a mock ViewModel that we’ve already stubbed out (i.e. mocked and setup return values/method calls/etc.). So, our TestPlantListFragment() is simply this:
class TestPlantListFragment : PlantListFragment() {
override fun initializeViewModelFactory() {
/* do nothing instead */
}
}
Making this come full circle, we can now “inject” whatever ViewModel we want with this util class:
What does this do exactly? Well, it allows us to get a ViewModelFactory that specifically creates whatever ViewModel we pass in when this is called in our Fragment:
ViewModelProviders.of(this, viewModelFactory).
get(PlantListViewModel::class.java)
Now we can go ahead and stub out our mock ViewModel and expect the Fragment under test to use it. Here’s what that looks like in our test:
And finally this is what one of our UI tests might look like:
I prefer doing UI tests this way because it mirrors how I setup my unit tests as well — great if you’re looking for consistency across UI and unit tests.
Here’s the entire Instrumented test file for PlantListFragment:
And finally, the full source for the Sample App can be found here.
This article is largely based off Yigit’s GithubBrowserSample and his approach to Android UI testing. I found it to be extremely helpful and can’t recommend trying to understand his approach enough.
Note: A major difference between our approaches is that I decided not to use dagger-android and opted for just Dagger 2.
Thanks for reading!