Unit testing under Android seems to be a big topic of discussion. Each weeks brings new blog posts, screencasts, tweets and presentations telling people how to handle the “difficulties” of unit testing under Android. Searching the Android Weekly newsletter for testing gives 6 pages of results!
I’m going to go out on a limb and make some bold claims, starting with this:
People are over complicating testing
There is no reason testing needs to be so difficult. You shouldn’t need dependency injection frameworks, mocking toolkits and UI automation APIs. You shouldn’t need much more than AndroidJUnitRunner + InstrumentationRegistry and even that is probably overkill.
At its core a unit test needs to:
- Set up the pre-conditions
- Send in input
- Assert the output is correct
If you are having a hard time doing these 3 things, the problem isn’t that you need a mocking toolkit or UI automation. My second bold claim is:
If an app is hard to test, the problem is architectural.
The reason people struggle to unit test under Android is they put too much code in places where:
- Its hard to setup the pre-conditions – eg, inside an Activity or Service, which means the app needs to be running.
- You can’t easily send input – eg, The user needs to type something or click somewhere.
- It’s hard to get at the output – eg, The UI changes or an animation plays
The solution to this is not mock objects or UI automation. The solution is to refactor the code. Extract out the complex logic, and put it where it’s isolated, reusable and testable.
For example, let’s say we have a UI with some complex validation rules. Certain combinations of fields need to be filled out. There are dependencies between the fields. The rules change based on the date and whats stored in the device DB. The easiest place to put the validation code is in a click listener on the submit button. However we won’t be able to test. We don’t have control over the pre-conditions, sending input is hard as is asserting the output.
It also happens we are adding more responsibilities into our Activity – suddenly it displays the UI AND implements validation – a god object in the making. The validation code is not isolated from the rest of the app. Its also not reuseable. Maintenance and reasoning about the code is going to be harder.
Our issue with testing has thrown light on deeper architectural issues.
A good solution is to push the validation logic deeper into app, away from the UI. Perhaps there is a domain object, or a POJO the validation can live on? Do we have a Presenter in an MVP architecture? A service layer the validation code could be added to? Ultimately though, it doesn’t need to be much harder than an object with a method:
List<ValidationError> validateOurComplexForm(String inputOne, int inputTwo, Date currentDate, boolean someValueFromTheDB)
Suddenly the pre-conditions are easy: just construct our object. Inputs are simple: The method params. Asserting the output is straightforward.
Finally by refactoring to make our testing easy, there have been some happy side effects: Our code is now much easier to read and reason about. We have a better separation of concerns and the Activity is not growing into a god class. The validation code is reusable by other parts of our app. Rather than going down a rabbit hole of complexity by adding libraries to help with testing, everything is instead simplified.
Further reading and acknowledgements
The Philosophical Hacker has a great series or articles on testing android apps, diving into these issues in much more detail. I don’t necessarily agree with all his conclusions (Mock objects and dependency injection! ) but the early articles analysing the cause of the problem ( Introduction, Part1 and Part2 ) are great.
Ken Scambler’s blog To Kill a Mockingtest has been quite influential in how I think about testing and architecture.
If you would like to know more about the application architecture I use and how testing fits into it I have a presentation on the subject.