Unit testing in Android with JUnit
- Vogella: Unit Testing with JUnit
- Junit Annotations: java2novice.com
- Assert Class: junit.org
- JUnit Api: tutorialspoint.com
- Anroid testing Medium.com posts
Creating Local unit tests
Place your test classes here:
Example test class
The test class, you can create several test classes and place them inside the test package.
The test method, several test methods can be created inside a test class.
Notice the annotation
The Test annotation tells JUnit that the public void method to which it is attached can be run as a test case.
There are several other useful annotations like
@After etc. This page would be a good place to start.
Annotation Information for JUnit Test:
@Test: The Test annotation tells JUnit that the public void method to which it is attached can be run as a test case. To run the method, JUnit first constructs a fresh instance of the class then invokes the annotated method.
@Before: When writing tests, it is common to find that several tests need similar objects created before they can run. Annotating a public void method with
@Before causes that method to be run before the Test method.
@After: If you allocate external resources in a Before method you need to release them after the test runs. Annotating a public void method with
@After causes that method to be run after the Test method. All
@After methods are guaranteed to run even if a Before or Test method throws an exception
Tip Quickly create test classes in Android Studio
- Place the cursor on the class name for which you want to create a test class.
- Press Alt + Enter (Windows).
- Select Create Test, hit Return.
- Select the methods for which you want to create test methods, click OK.
- Select the directory where you want to create the test class.
- You're done, this what you get is your first test.
Tip Easily execute tests in Android Studio
- Right click test the package.
- Select Run 'Tests in ...
- All the tests in the package will be executed at once.
JUnit can also be used to test if a method throws a specific exception for a given input.
In this example we will test if the following method really throws an exception if the Boolean format (input) is not recognized/unknown:
By adding the
expected parameter to the
@Test annotation, one can define which exception is expected to be thrown. The unit test will fail if this exception does not occur, and succeed if the exception is indeed thrown:
This works well, however, it does limit you to just a single test case within the method. Sometimes you might want to test multiple cases within a single method. A technique often used to overcome this limitation is using
try-catch blocks and the
Note: Some people consider it to be bad practice to test more than a single case inside a unit test.
Getting started with JUnit
To start unit testing your Android project using JUnit you need to add the JUnit dependency to your project and you need to create a test source-set which is going to contain the source code for the unit tests. Projects created with Android Studio often already include the JUnit dependency and the test source-set
Add the following line to your module
build.gradle file within the dependencies
JUnit test classes are located in a special source-set named
test. If this source-set does not exist you need to create a new folder yourself. The folder structure of a default Android Studio (Gradle based) project looks like this:
If your project doesn't have the
/app/src/test folder you need to create it yourself. Within the
test folder you also need a
java folder (create it if it doesn't exist). The java folder in the
test source set should contains the same package structure as your
If setup correctly your project structure (in the Android view in Android Studio) should look like this:
Note: You don't necessarily need to have the
androidTest source-set, this source-set is often found in projects created by Android Studio and is included here for reference.
Writing a test
Create a new class within the
Right click the test source-set in the project view choose
The most used naming pattern is to use the name of the class you're going to test with
Testadded to it. So
@RunWithannotation is needed in order to make JUnit run the tests we're going to define in our test class. The default JUnit runner (for JUnit 4) is the
BlockJUnit4ClassRunnerbut instead of using this running directly its more convenient to use the alias
JUnit4which is a shorthand for the default JUnit runner.
Create a test
A unit test is essentially just a method which, in most cases, should not fail if run. In other words it should not throw an exception. Inside a test method you will almost always find assertions that check if specific conditions are met. If an assertion fails it throws an exception which causes the method/test to fail. A test method is always annotated with the
@Testannotation. Without this annotation JUnit won't automatically run the test.
Note: unlike the standard Java method naming convention unit test method names do often contain underscores.
Running a test
If everything is setup correctly JUnit starts running the method and you should see the following interface within Android Studio:
You can also run all the tests defined in a single class, by right clicking the class in the project view and clicking
Run 'StringUtilitiesTest 'or use the keyboard shortcut
ctrl+shift+f10if you have selected the class in the project view.
If you wan't to run all the tests defined in the project or in a package you can just right click the package and click
Run ...just like you would run all the tests defined in a single class.
Moving Business Logic Out of Android Componenets
A lot of the value from local JVM unit tests comes from the way you design your application. You have to design it in such a way where you can decouple your business logic from your Android Components. Here is an example of such a way using the Model-View-Presenter pattern. Lets practice this out by implementing a basic sign up screen that only takes a username and password. Our Android app is responsible for validating that the username the user supplies is not blank and that the password is at least eight characters long and contains at least one digit. If the username/password is valid we perform our sign up api call, otherwise we display an error message.
Example where business logic is highly coupled with Android Component.
Example where business logic is decoupled from Android Component.
Here we define in a single class, LoginContract, that will house the various interactions between our various classes.
Our LoginActivity is for the most part the same except that we have removed the responsibility of having to know how to validate a user's sign up form (our business logic). The LoginActivity will now rely on our new LoginPresenter to perform validation.
Now your business logic will reside in your new LoginPresenter class.
And now we can create local JVM unit tests against your new LoginPresenter class.
As you can see, when we extracted our business logic out of the LoginActivity and placed it in the LoginPresenter POJO. We can now create local JVM unit tests against our business logic.
It should be noted that there are various other implications from our change in architecture such as we are close to adhering to each class having a single responsibility, additional classes, etc. These are just side effects of the way I choose to go about performing this decoupling via the MVP style. MVP is just one way to go about this but there are other alternatives that you may want to look at such as MVVM. You just have to pick the best system that works for you.
JUnit defines quite some
assertEquals methods at least one for each primitive type and one for Objects is available. These methods are by default not directly available to call and should be called like this:
Assert.assertEquals. But because these methods are used so often people almost always use a static import so that the method can be directly used as if it is part of the class itself.
To add a static import for the
assertEquals method use the following import statement:
You can also static import all assert methods including the
assertFalse etc. using the following static import:
Without static import:
With static import: