Unit Testing in Android Studio with Kotlin and JUnit

Unit Testing in Android Studio with Kotlin and JUnit

Introduction

Today when working in an agile development project, the stakeholders usually wants us to deliver features rapidly and frequently. We also need to ensure a high quality level of the released features and be able to safely refactor the code base due to changed requirements.

This makes it impossible to rely only on manual testing, and we need to automate as much testing as possible.

One type of automated testing is the unit test, where we isolate one unit (module/class) at a time, and test that it behaves as aspected. Sounds simple right!

There are a lot of tools and frameworks available to make it easy to configure, mock and unit test out there. In this article we will have a quick look at JUnit, Mockito and Dagger.

We at Avalon Innovation are more than 250 specialists all over Scandinavia who are passionate about developing ground-breaking, innovative product and system solutions that really make a difference. Since 1997 we have tackled many challenging and difficult assignments, which have provided us with broad technical expertise and unique insights into innovation.

The Demo Project

Download the demo project from my GitHub for the complete source code from this article.

Dependency Injection

When we want to test a single unit we often need to remove or replace that units dependencies. This is called mocking! We replace the dependencies with something else that behaves in a predictive way.

Dependency injection or Inversion of Control (IoC) is an architectural pattern where a units dependencies are provided to the unit instead of being created inside the unit.

Lets look at an example!

interface ProfileServiceInterface {
    fun getProfile(): Profile?
    fun saveProfile(profile: Profile)
    fun hasProfile(): Boolean
}

class ProfileService @Inject constructor (private val profileRepository: ProfileRepositoryInterface): ProfileServiceInterface {
    override fun getProfile(): Profile? {
        return profileRepository.getProfile()
    }

    override fun saveProfile(profile: Profile) {
        profileRepository.saveProfile(profile)
    }

    override fun hasProfile(): Boolean {
        return profileRepository.getProfile() != null
    }
}

In the example above the class ProfileService is dependent on the interface ProfileRepositoryInterface, and that gets injected to the constructor. This is called constructor injection and is a common way to do dependency injection.

This is really great because now ProfileService is only aware of ProfileRepositoryInterface and not with the exact implementation! In production it will probably be implemented with a class that fetches and saves profile information to a database or a web service, but in a unit test scenario we can send a mocked implementation of ProfileRepositoryInterface to ProfileService to isolate it from its dependency!

In this demo project I used Dagger 2 for the configuration of dependencies. A tool is perhaps not necessary, but it makes our job a bit easier.

Testing a simple unit

The demo project has a class called Profile that contains both data about a users profile and some calculations of daily recommended energy, carb, fat and protein intake. These calculations are perfect for unit testing!

The Profile class does not have any external dependencies that we need to mock, we just need to create an instance, add some data, run the calculations and validate the results.

class TestProfileCalculations {
    private lateinit var maleProfile: Profile


    @Before
    fun initTests() {
        maleProfile = Profile()
        maleProfile.birthDate = LocalDateTime.now().minusYears(47)
        maleProfile.gender = GenderTypes.Male
        maleProfile.weight = 105
        maleProfile.height = 183
        maleProfile.physicalActivity = PhysicalActivityTypes.Moderate
    }


    @Test
    fun testCalculateDailyEnergyIntakeMale() {
        // ARRANGE


        // ACT
        val result = Math.round(maleProfile.calculateDailyEnergyIntake()).toInt()


        // ASSERT
        assertEquals(3268, result)
    }
}

Lets create a class called TestProfileCalculations! The method marked with @Before is used to initialize something before the tests are run. This is the place where we create a new instance of the class Profile.

Next we create a test method. In this case we want to test that the calculated daily energy intake for this profile is 3268 kcal. We know that this is the correct value and we want to test that the calculations are correct.

A unit test usually consists of three steps. Arrange data before the test, act on the subject under test and assert that the results of the test is what we expected.

In this case we don't need to arrange any more, we have the created profile. Instead we call the method and assert that the returned value is equal to 3268 kcal. Done!

Mocking dependencies

In the demo project we have a class called ShowProfileViewModel that calls a couple of methods in ProfileServiceInterface to load and save a users profile. We need to write a test to ensure that this is done in a correct way!

interface ShowProfileViewModelInterface {
    var currentProfile: Profile?


    fun loadProfile()
    fun saveProfile()
}


class ShowProfileViewModel @Inject constructor (private val profileService: ProfileServiceInterface): ShowProfileViewModelInterface {
    override var currentProfile: Profile? = null


    override fun loadProfile() {
        if (profileService.hasProfile()) {
            currentProfile = profileService.getProfile()
            return
        }


        currentProfile = Profile()
    }


    override fun saveProfile() {
        if (currentProfile == null) {
            return
        }


        profileService.saveProfile(currentProfile ?: Profile())
    }
}

There are some logic here.

When loadProfile() is called and a profile is missing, a new one is created and the loaded or created profile is then accessible with the property currentProfile.

When saveProfile() is called the data in currentProfile is sent to ProfileServiceInterface for processing.

Lets write some tests!

private lateinit var subject: ShowProfileViewModelInterface
private lateinit var profileService: ProfileServiceInterface

First we create a couple of variables! One for the subject and one for ProfileServiceInterface.

@Before
fun initTests() {
    profileService = mock()

    subject = ShowProfileViewModel(profileService)
}

Then we setup the tests with a couple of steps.

  1. We use Mockito to create a mock object from ProfileServiceInterface. Mockito makes it easy for us to configure in runtime how the mock objects behave.
  2. We create a new instance of ShowProfileViewModel as the subject and send the mocked ProfileServiceInterface to its constructor.

Now we have an instance of the test subject (ShowProfileViewModel) and a mocked instance of ProfileServiceInterface.

@Test
fun `When loading a profile and no profile exists an empty profile shall be returned`() {
    // ARRANGE
    `when`(profileService.hasProfile()).thenReturn(false)


    // ACT
    subject.loadProfile()


    // ASSERT
    assertNotNull(subject.currentProfile)


    assertEquals(Profile.defaultBirthDate(), subject.currentProfile?.birthDate)
    assertEquals(Profile.defaultGender(), subject.currentProfile?.gender)
    assertEquals(Profile.defaultPhysicalActivity(), subject.currentProfile?.physicalActivity)
    assertEquals(0, subject.currentProfile?.weight)
    assertEquals(0, subject.currentProfile?.height)
}

Lets test that ShowProfileViewModel behaves correctly when we call loadProfile().

We start with configuring that the method hasProfile() in ProfileServiceInterface should always return false. This is one of the nice things that Mockito gives us! The ability to change mocked objects behavior easily.

Then we act on the test subject and call the method loadProfile().

Now because a profile does not exist ShowProfileViewModel will create a new instance of Profile, right? This we can assert!

@Test
fun `When loading a profile and a profile exists that profile shall be returned`() {
    // ARRANGE
    val profile = Profile()
    profile.birthDate = LocalDateTime.now().minusYears(45)
    profile.gender = GenderTypes.Male
    profile.weight = 105
    profile.height = 183
    profile.physicalActivity = PhysicalActivityTypes.Moderate


    `when`(profileService.hasProfile()).thenReturn(true)
    `when`(profileService.getProfile()).thenReturn(profile)


    // ACT
    subject.loadProfile()


    // ASSERT
    assertNotNull(subject.currentProfile)


    assertEquals(profile.birthDate, subject.currentProfile?.birthDate)
    assertEquals(profile.gender, subject.currentProfile?.gender)
    assertEquals(profile.physicalActivity, subject.currentProfile?.physicalActivity)
    assertEquals(profile.weight, subject.currentProfile?.weight)
    assertEquals(profile.height, subject.currentProfile?.height)
    assertEquals(45.toLong(), subject.currentProfile?.age)
}

Now we can continue to write a test for when loadProfile() is called and a profile exists. hasProfile() should return true and getProfile() should return a known profile so we can assert that the correct profile is added to ShowProfileViewModel. Done!

For more examples please look at the demo project on GitHub!

Conclusions

Unit testing is necessary to deliver features frequently while ensuring a high quality of the delivered product. Unit tests are easy to write if we use an architecture that enables us to mock dependencies and isolate the units that needs to be tested.

More info on the frameworks used in this article can be found online:

https://junit.org/junit4/

https://site.mockito.org/

https://google.github.io/dagger/

Happy testing!

To view or add a comment, sign in

More articles by Jonas Frid

  • We need to stop pretending that everyone is needed at work

    We are facing a transformation greater than industrialization. Yet we still talk as if work is the only way to earn a…

    5 Comments
  • Är 2026 det sista året vi byggde UI?

    Det finns en anledning till att det här är svårt att greppa. Vi har byggt mjukvara på samma sätt i över 40 år: System…

  • Take your App to the next Step with Microsoft App Center

    Introduction In this article I will give you an introduction on how to use Microsoft App Center to manage everything…

  • An Introduction to Microsoft Azure DevOps

    Introduction In this article I will give you an introduction to DevOps and how to use Microsoft Azure DevOps to build…

    2 Comments
  • Unit Testing in Xamarin with NUnit

    Introduction Today when working in an agile development project, the stakeholders usually wants us to deliver features…

  • Custom hit test in Xamarin.iOS

    Views can catch touch events, but normally that is done in a rectangle! What if you want a round image and only capture…

  • MvvmMobile! Just another MVVM framework? ;-)

    Introduction MvvmMobile is an MVVM framework specifically developed for Xamarin iOS and Xamarin Android with focus on…

  • Range Slider In Xamarin Forms

    Intro Earlier this year I found myself in need of a range slider for a Xamarin Forms project. I found a couple of…

    1 Comment
  • Bindable Grid In Xamarin Forms

    Intro Xamarin Forms is a great platform for building native cross-platform apps for iOS, Android and Windows, but…

    5 Comments

Others also viewed

Explore content categories