Bloco

View Original

How to test Android Apps: Architecture

How you develop your application influences how easy is to test it. Likely, you already know the best practices:

  • Classes should have a single purpose
  • Code should be extendable
  • Avoid having too much dependencies
  • Separate your data layer from your business layer
  • ...

All that helps here. But for easy unit testing, classes need to be decoupled. Classes must specify their needs in abstractions (interfaces). And classes should not be responsible for constructing their own dependencies. Basically, it's just about following the dependency inversion principle.

This means that your objects dependencies are passed in the constructor, so they can be easily changed, a very common process for testing.

Avoid:

class Foo {
    private val something = Something()
}

And instead use:

class Foo(
    val something: Something
) {
 // ...
}

Sounds good right? But what if you have a complex application, with lot of nested dependencies? You can quickly end up writing too much code just to initialize your objects. That's why you need a dependency injection library.

Dependency Injection

For Android, the most popular dependency injection is the Dagger library. However in the last years Google as developed a more tailored version for Android called Hilt.

I won't go into the details of integrating Dagger into your application, but you can check our Template that comes configured with it, or the official documentation.

After the setup, you just need to annotate the constructor for injection like:

class GetAndSetCounter
@Inject constructor(
    private val preferences: CounterRepository
) { 
 // ...
}

And when you inject an instance of CountPresenter in another class, it will already come with countRepository automatically instantiated for you.

What about objects you don't have access to the construtor? Again we won't go into details here about Dagger/Hilt but those you will have to provide the objects yourself, usually you do that in "Dagger Modules".

Testing Strategy

At first, it might seem that functional tests is all you need. Test your application just like any user would, and you're covered. But you will find shortcomings:

  • Functional tests are 10x slower
  • It's hard to mock external components like web servers, location, etc..
  • When a test fails, you still have to find where the bug is

With unit testing, tests run faster and errors are found faster. And if you keep dependencies decoupled, you can easily mock everything you don't want to test.

A common strategy is to write functional tests for the common scenarios of your application. A sort of smoke test to make sure things are working together correctly. And then, write unit tests to cover all variations of your classes.

Changing implementations

In a normal project, code is going to be refactored and implementations are going to change. Unit tests do need more maintenance to be kept updated, though they are worth it in the long run.

A few tips that help ease the load:

  1. Only test the code inside each class separately, so a single bug doesn't break all your tests
  2. Specify your classes dependencies with Interfaces, instead of concrete classes
  3. Write more and smaller tests, even for the same method, to split behaviors.

You can use Mockito for mocking dependencies and validate calls. The following posts will have code examples.

Resources


How to test Android Apps

  1. Introduction
  2. Architecture
  3. Unit Testing
  4. Instrumentation Testing
  5. Other details