Automate UI testing with predictable state and flexibility, off the UI thread
Goals
- Fully automate UI testing (Espresso on Android)
- All computation (except view access) done on a background thread
- A front-end architecture that can fit any platform. The same ideas apply to iOS, Android, & the web, thanks to ReactiveX’s cross-platform nature.
- A UI layer that can adapt to anything. Edge cases, new requirements, and increased complexity do not require refactoring
This article covers goals #1 and #2. In a future post, I’ll dig into why #4 is true.
Model View Intent (MVI)
I definitely recommend checking out Hannes Dorfmann’s amazing blog series on MVI and Android. I won’t get in to what Model View Intent is, but rather my specific implementation of it.
In a nutshell, we will merge input from our data layer with user input to output a continuously updated
ViewStateover time, rendering each new instance of aViewStateonto ourUi/View.
Demo App — a “Deck of Cards”
Here’s the simplified implementation ofStateRenderer<DealCardsUi.State>.
Using Rx’s Schedulers and observing the latest ViewState, we achieve Goal #2 — stay off the UI thread as much as possible.
Automated UI Testing
All of our Ui classes have the following function — a single point of entry for displaying information to the user.
fun render(state: ViewState)
Testing is reduced to a simple input/output function.
- Input — the
ViewState. Grab a reference to yourUi, and call theui.render(viewState)function. - Output — the
Ui. Use Espresso to verify theUilooks as expected.
Want to test configuration changes? Call activity.recreate() and verify the output is unchanged again.
Meet the following requirements to simplify testing.
- Unhook (disable) every
Presenterfrom activating during testing, and/or disable your disk/network layer. - Keep navigation functional without presenters. Navigation via intents simplifies this.
- Ability to get a reference to your view. This could be an
Activity,Fragment,ViewGroup,Controller, etc - but you must be able to call yourview.render(state: ViewState)function.
Conclusion
I truly believe this style of view architecture is the natural evolution over MVP, MVVM, etc. A single ViewState allows for predictable state and maximum testability.
In future articles, I will dig deeper into other components, such as the business logic that is responsible for the ViewState.
Catch the conversation on Reddit
All source is available on GitHub.
Shoutout to the many pioneers of reactive programming that have made this architecture possible!
Additional resource — watch Jake Wharton’s awesome talk on managing state with Rx.