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
ViewState
over time, rendering each new instance of aViewState
onto 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 theUi
looks 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
Presenter
from 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.