Unit Testing for Android : A Beginner’s Guide

Asad Mahmood
5 min readJul 23, 2023

--

“ONE TEST IS MORE THAN ZERO TESTS”

Unit testing is a crucial phase of software development. It brings about a development paradigm known as Test Driven Development (TDD). These tests typically test the business logic of applications.

If you are not writing tests, you are writing an instant legacy code

Why do we write unit tests?

  1. We make mistakes.
  2. We want our code to work.
  3. We want to develop faster with more confidence and fewer regressions.

When it comes to Android, and the various mobile platforms in general, app testing can be a challenge. Implementing unit tests and following principles of test-driven development or similar can often feel unintuitive, at the least. Nonetheless, testing is important, and shouldn’t be taken for granted or ignored.

Let’ s jumps to some basic of unit testing When it comes to Android.

Package structure

When you create a new Android project, you get the following three source sets by default. They are:

  • main: Contains your app code.
  • androidTest: Contains tests known as instrumented tests.
  • test: Contains tests known as local tests.

The difference between local tests and instrumented tests is in the way they are run.

Local tests (test source set)

These tests are run locally on your development machine’s JVM and do not require an emulator or physical device. Because of this, they run fast, but their fidelity is lower, meaning they act less like they would in the real world.

Instrumented tests (androidTest source set)

These tests run on real or emulated Android devices, so they reflect what will happen in the real world, but are also much slower.

Test runner

A test runner is a JUnit component that runs tests. Without a test runner, our tests would not run. There’s a default test runner provided by JUnit that we get automatically.

android {

defaultConfig {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
}

Android Studio gives you tools to generate test that helps you implement the tests for your class. Right click class that is being tested and select Generate > Test.

After click Test you will be able to create Test class.

Let’s go through some annotations that will use in test class.

  • @Test: This annotation is used to mark a method as a test case. The @Test annotation tells the Java compiler to run the method as a test case when the class is compiled.
  • @Before: This annotation is used to mark a method as a setup method. The @Before annotation tells the Java compiler to run the method before each test case in the class. This is useful for setting up the test environment before each test case is run.
  • @After: This annotation is used to mark a method as a tear-down method. The @After annotation tells the Java compiler to run the method after each test case in the class. This is useful for cleaning up the test environment after each test case is run.
  • @Ignore: This annotation is used to mark a method as an ignored test case. The @Ignore annotation tells the Java compiler to not run the method as a test case. This is useful for temporarily ignoring a test case that you are not yet ready to run.

An assertion is the core of your test. It’s a code statement that checks that your code or app behaved as expected. In this case, the assertion is assertEquals(4, 2 + 2) which checks that 4 is equal to 2 + 2.

Testing strategy

There are a few other strategies you can use for writing readable tests. Both of which are demonstrated in the test you just wrote.

Given, When, Then

One way to think about the structure of a test is to follow the Given, When, Then testing mnemonic. This divides your test into three parts:

  • Given: Setup the objects and the state of the app that you need for your test. For this test, what is “given”.
  • When: Do the actual action on the object you’re testing.
  • Then: This is where you actually check what happens when you do the action where you check if the test passed or failed. This is usually a number of assert function calls.

Note that the “Arrange, Act, Assert” (AAA) testing mnemonic is a similar concept.

       @Test
fun getUserProfile_givenUserId_returnUser() {
// GIVEN
val userId = 1
val user = User(userId,"","TestUser")
// WHEN
doReturn(Observable.just(user)).`when`(webservice).getMyProfile(userId)
presenter.getUserProfile(anyString())
// THEN
verify(profileView).render(ProfileState.DataState(user))

}

Dependency configuration

Usually, you use implementation when adding a dependency. When you are ready to share your app with the world, it is best not to bloat the size of APK with any of the test code or dependencies in our app. You can designate whether a library should be included in the main or test code by using gradle configurations.

The most common are:

  • implementation—The dependency is available in all source sets, including the test source sets.
  • testImplementation—The dependency is only available in the test source set.
  • androidTestImplementation—The dependency is only available in the androidTest source set.

Test Doubles

The solution to this is that when you’re testing the repository, don’t use the real networking or database code, but to instead use a test double. A test double is a version of a class crafted specifically for testing. It is meant to replace the real version of a class in tests. It’s similar to how a stunt double is an actor who specializes in stunts, and replaces the real actor for dangerous actions.

Here are some types of test doubles:

Fake

A test double that has a “working” implementation of the class, but it’s implemented in a way that makes it good for tests but unsuitable for production.

Mock

A test double that tracks which of its methods were called. It then passes or fails a test depending on whether it’s methods were called correctly. When you mock an object it creates an empty implementation of that class.

Naming Convention

The name of the test should help you understand what the test does. The naming convention should look like this:

subjectUnderTest_actionOrInput_resultState

  • Subject under test is the method or class that is being tested (getUserProfile).
  • Next is the action or input (givenUserId).
  • Finally you have the expected result (returnUser).

Pro Tip

When you have repeated setup code for multiple tests, you can use the @Before annotation to create a setup method and remove repeated code.

Thanks for reading this article. Be sure to click 👏 below to applause this article if you found it helpful. It means a lot to me. You can write me via comments, Twitter , or Linkedin. Happy coding and have a good time!

Buy me a Coffee

--

--