BMI Calculator - Testing

BMI Calculator – Testing with Koin

In this lecture of the BMI Calculator sample application we are going to talk first about how does tests work in general on Android then how you do testing with Koin.

You will learn about the following topics:

      • Setup and run tests
        • Unit tests
        • Instrumented tests
      • Some code concepts
        • JUnit4
        • Hamcrest
        • AndroidX test library
        • AndroidX Architecture Components Core Test Library

GitHub

The starter project for this lecture is available on GitHub. Just download it using the below link.

GitHub – branch implement_koin

After the download, extract the app from the .ZIP file and import it in Android Studio (File -> New -> Import Project…)

Step 1 - Introduction of testing

In Android you can write unit and instrumented tests.

For unit tests or local tests you don’t need to have an emulator or phsysical device, because it runs on our development machine’s JVM. Because of this, they run faster, but the fidelity is lower and they act less like they would in the real world.

For instrumented tests you have to have an emulator or a physical device. So they are closer what will happen in the real world. Because of this they are much slower then unit tests.

In Android studio tests have thier speciel icons.

A unit test is represented by a green-red triangle icon.

An instrumented test is represented with a red-green triangle also, but there is the Android icon as well.

Next we are going to check out where you can find and create tests for your application.

If you open up the Project pane from the left part of Android Studio, then there you should have 3 folders. You can see them on this picture.

The name of this folders are source sets.

      • In the main source set you can find your app code.
      • In the androidTest source set folder are the instrumented tests.
      • And finally in the tests source set folder are the unit or local tests.

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

Step 2 - Add dependencies

For testing we need some more dependencies, which help us to run specific tests.

So, from the left Project pane open the Module build.gradle file and go to the dependencies {} section. There we have inserted in the chapter 2 some dependencies.

After these dependencies paste the below lines.


Dependencies for testing

Note that here we use testImplementation insteed of implementation. When we share our app in the app store then it is best not to enlarge the size of the APK with any of the test code or dependencies in our app.

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

Source: Testing basics

As you can see, we have added testing dependencies for Koin, AndroidX, Navigation, Fragments. But there are two more dependencies, Hamcrest and Espresso.

Hamcrest help us to write more readable tests. For example we can us an is word in assertion like this: assert thet the calculated bmi value is 26.9.

The Espresso testing framework, provided by AndroidX Test, provides APIs for writing UI tests to simulate user interactions within a single target app. A key benefit of using Espresso is that it provides automatic synchronization of test actions with the UI of the app you are testing.

Next we are going to define the version of the above dependencies.

For this open the Project build.gradle from the left Project pane and paste the below variables into the buildscript{} section. There you should have again the previously defined versions.


Versions for testing

Finally click on the Sync now button in the top right corner of Android Studio.

Step 3 - Unit testing

We are going to start the practical part of testing by created some unit tests.

Create the first test file

In the first test we gonna see how does testing works in Android by calculating the BMI value based on the height and weight.

So, open up the BMI data class from the model package, what you can find in the main source set.

Thenafter click with the right mouse button on the name of the class.

From the popup window click on the Generate… option, then select Test…

In the popup window select JUnit4 in the line of Testing library. JUnit4 is the appropriate testing library. Then click on the OK button and a new window will open.

In this window select the second green folder which is for local testing. Because we are doing math calculations and it won’t include any Android specific code. It means, we won’t need a physical device or emulator.

If you are done, then click again on the OK button at bottom of the window.

Given, When, Then

There are some possibilities to write readable tests. In this tutorial we are going to us the Given, When, Then testing mnemonic.

Given: Setup the objects and the state of the app that we need for our test. For this test, what is “given” is an instance of the BMI::class.

When: Do the actual action on the object we are testing. For this test, it means calling the bmi() method.

Then: This is where we actually check what happens when we do the action and where we check if the test passed or failed. This is usually a number of assert function calls. For this test it will be one test which checks the result of the BMI’s calculation.

The first test

Given

First we are creating one instance of the BMI class with defined values for its properties.

So, copy and paste the below variable into the BMITest::class.


Instance of BMI

When

Next we are going to create a function, what will be annotated by @Test, which indicates it is a test and comes from JUnit.

Thenafter comes the calculation of the bmi’s value by calling the bmi() method on the bmi_1 variable.

So now, the function looks like below.


When

Then

Then the next part is the core part of a test, which is to check that our code/app works as expected.

Here we are asserting thet the result of the BMI value will be 36.9 after the calculation. With this result the test will surly fail, but you gonna see a failed test.

So, add the below line to the getCalculatedBmi_returnsBmi() method.


Then

In tests you should be always more careful to add the right import to the file. In our case we need the right Hamcrest import:

import org.hamcrest.Matchers.`is`

Run the test

It’s time to run our first test. In Android Studio at the left side of the code you should have a green triangle.

In test you can use it to run a test. You can run only one function, or all of the functions inside of the corresponding class.

After the run our test should fail. In this case Android Studio will tell you what was the expected result and what was the real result of the function.

To have in our case a passed test we should change the expected result. Of course in a real app you should check deeply the error, but in our case we know thet the problem is the expected value from us.

We have the height of 168 cm and the weight of 76 kg. The result BMI value should be like 26.927439.  So go to the getCalculatedBmi_returnsBmi() method and change the expected value from 36.9f to 26.927439f and run again the test.

Yuhuu, the test passed.
Congratulations, this was your first successful test. 😎

Step 4 - Create fakes

In this step we are going to create some fakes to test the list of the BMIs, which are stored in the repository.

But why we need fakes and what does it mean? 🤔

Using fakes we don’t need any real data for example from a real network or database code.

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.

Testing codelab

FakeBmiRepository

In this step we are going to create a fake repository, where we can call the same function what we could use when we save or get back the data from the database.

So, in the test source set create a new package with the name: “data.source”, then there a new Kotlin file and name it as “FakeBmiRepository”.

Then extend the class by the BmiRepository::interface and implement the needed functions.

In this fake repository we won’t fetch the data from the Room database, insteed we will pass a mutable list as the constructor of the class. So add the below list to the constructor.

var bmis: MutableList<BMI>? = mutableListOf()

Next, we are defining a new function to get back the list.

suspend fun getListOfBmi() = bmis

For now that was all about the fake repository. Next we are going to create a test class for the BmiRepository::class.

BmiRepositoryTest

So go to the main source set and open the BmiRepositoryImpl::class. Click on the name of the class with the right mouse button, select the Genereate…, then the Test… option.

We gonna need here again the JUnit4 as the testing library.

Click on the OK button and in the next screen select the test source set.

First of all we are going to create here 2 bmi variables.


Instances of BMI

Thenafter a list of the above bmi varaibles which are sorted by its calculated BMI values.

Finally a lateinit variable of the fake BMI repository.


The list and the repository

@Before

Next we are going to init the repository’s variable using a function. This function will get the @Before annotation. It means, thet this function will run always before the functions, what we have annotated by @Test.

You can have a function when all the test finished and in this case annotate this function with the @After annotation.

But in this class we gonna have only the @Before annotation.

Copy and paste the below function after the member variables.


Run before tests

@Test

The last step of the fake repository test is to define the test case.

Before the implementaion we should talk about how we can run coroutines in a test environment.

It is very easy, because with the runBlocking function we can create a coroutine scope. It takes in a block of code and then runs this block of code in a special coroutine context which runs synchronously and immediately, meaning actions will occur in a deterministic order. This essentially makes your coroutines run like non-coroutines, so it is meant for testing code.

This function comes from the test Kotlin coroutine dependency, what we have implemented in the Modul build.gradle file at the beginning of this chapter.

First we gonna get the list from the repository, then we will assert that this list is equal to the listOfBmis.

Copy and paste the below function into the BmiRepositoryImplTest::class.


Assertion

Run the test

It is time to run this test also. So click again on the green triangle which is to the left of the code in Android Studio.

The test should pass again. 😊

Step 5 - Instrumented tests

After unit testing, we are going to create some instrumented tests as well. For these tests you will need an emulator or a physical device to run, because these tests need the Android system.

We are going to test in the BmisFragment the navigation to the AddNewBmiFragment. Then we gonna make a test for the longclick on an item of the RecyclerView.

Create new test file

So, open up the BmisFragment::class from the main source set’s bmis package. Click again on the name of the class with the right mouse button, select Generate… and then the Test… option.

In the popup window we won’t change anything, so click on the OK button.

Thenafter select here the androidTest source set in the second popup window.

Member variables

In this class we gonna have 2 member variables.

The first one will instantiate a TestNavHostController. Using this class we can test the fragment interactions with their NavController in isolation.

The second variable will be a FragmentScenario instance. This allows us to verify a fragment’s state and interactions in both unit and instrumentation tests.

So, copy and paste the below variables into the the BmiFragmentTest::class.


Member variables

@Before

Next we are goin to create a function what will run before the tests.

This function will again initialize the above variables, set the navigation graph of the app. Set the currently open destination.

Then we are creating a graphical FragmentScenario for the BmiFragment, which will supply the theme as well.

Supplying the theme is necessary because fragments usually get their theming from their parent activity. When using FragmentScenario, your fragment is launched inside a generic empty activity so that it’s properly isolated from activity code (you are just testing the fragment code, not the associated activity). The theme parameter allows you to supply the correct theme.

Thenafter the last thing is to set the NavConroller property on the fragment. Paste the below function into the BmiFragmentTest::class.


@Before

First instrumented test

So, as we have talked about, our first test will test the navigation when we click on the Floating Action Button on the BmisFragment screen.

To find the view inside of the fragment we will use ViewMatchers::class, then to perform a click we can use the ViewActions::class.

Thenafter we are going to assert thet the fragment with the id addNewBmiFragment is the current destination. (The id comes from the navigation graph.)


Test FAB click

Run your third test

It’s time again to run this test also.

Before the run, be sure that you have at least one installed emulator or a connected physical device.

So click again on the green triangle which is to the left of the code in Android Studio.

Unfortunetly our test will fail. It does, because we start the fragment in isolation and the test doesn’t know anything about the parent Activity. To solve this issue, open up the BmisFragment::class from the bmis package of the main source set.

In the onStart() method we have a line which shows again the supportActionBar. In this line we tell for the app to use the parent activity as MainActivity, but in test this cast will fail.

So, to solve this issue, replace the line by the below if statement.


if statemnt for fragment's activity check

Next run again the test and it will pass.

Second instrumented test

The second instrumented test will be our last test in this chapter.

First, this test will make a longclick on an item of the RecyclerView. This click will open the Bottom Sheet Dialog where we can delete the selected item.

Thenafter we gonna assert thet the views are visible with defined text.

So, copy and paste the below function into the BmisFragmentTest::class.


Perform longclick

Run the last test

Run our last test also. If you have done it correctly, then it should be a passed test also.

GitHub

The finish project for this lecture is available on GitHub. Just download it using the below link.

GitHub – Test

After the download, extract the app from the .ZIP file and import it in Android Studio (File -> New -> Import Project…)

Questions

I hope the description was understandable and clear. But, if you have still questions, then leave me comments below! 😉

Have a nice a day! 🙂

Follow and like us:

Click to rate this post!

[Total: 0 Average: 0]

Leave a Reply

Your email address will not be published. Required fields are marked *