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.
- 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.
- 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!