Introduction to Junit and Mockito

December 13, 2020

If you’ve ever written any software in your life, you would know that bugs are an inevitable problem. One useful tool to help you resolve bugs faster is writing unit tests. A unit test is a piece of code used to test the smallest components of your program. Usually, this would refer to individual methods/functions.

In this tutorial, we will learn to use two popular Java frameworks for unit testing, i.e. JUnit and Mockito.

Prerequisites

Before beginning this tutorial, some basic knowledge of Maven will be needed. We will use it to get our dependencies for these frameworks. Additionally, some knowledge of the theory behind unit testing would be great but is not required. To get a more detailed article on unit testing click here.

Lastly, to make things easier make sure to use an IDE with JUnit integration. A couple popular Java IDEs like Eclipse and Intellij would allow you to easily run your tests. We will not go over the specifics of running tests in particular IDEs or with Maven.

Setup

To begin, we will be setting up a basic Maven project. To get the dependencies we need for these frameworks, add the following to your pom.xml file.

<dependencies>
   <dependency>
       <groupId>org.junit.platform</groupId>
       <artifactId>junit-platform-launcher</artifactId>
       <version>1.7.0</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
       <version>5.7.0</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.junit.vintage</groupId>
       <artifactId>junit-vintage-engine</artifactId>
       <version>5.7.0</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-api</artifactId>
       <version>5.7.0</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
       <version>3.6.0</version>
       <scope>test</scope>
   </dependency>
</dependencies>

As well, make sure you set up the appropriate Java version since JUnit 5 uses features of Java 8.

<properties>
   <maven.compiler.target>1.8</maven.compiler.target>
   <maven.compiler.source>1.8</maven.compiler.source>
</properties>

Now that we have gotten our dependencies installed, let’s begin. For simplicity, we will be writing unit tests for a class called Calculator. This will have methods for simple math operations that we will be testing. All our test files will be under src > main > test > java.

The architecture of JUnit tests

Before we get our hands dirty, let’s go over some basic theory behind JUnit and unit testing. When we write unit tests, each test is a separate method of a test class. Whenever we run a test, JUnit creates a new instance of our test class to invoke its corresponding method.

When designing our tests, we should ensure that they are independent of each other. Normally, one test cannot rely on another preceding or proceeding it. This is because with JUnit, we cannot predict the order in which it will run our tests. Suppose we wanted a test to alter an array for another test.

This would make our lives more difficult since it does not guarantee their order. There would be a good chance that the second test runs first so the first test does not alter the array for it. In theory, you could put them in order using the @Order annotation. Yet, it is best practice to keep tests independent.

Keeping your tests independent makes it possible to run your tests in parallel and is just cleaner to read. There may be more complications as well when one attempts to share a variable instance between tests. As mentioned earlier, before running each test JUnit creates a new instance of our test class.

Attempting to share instance variables between tests to have them affect each other would not work. Now that we got that out of the way, we can begin writing our first tests.

Writing our first unit tests

First, our calculator will need a basic addition method to add two integers. As a good unit testing practice, we should aim for our tests to try dealing with every method specification. In particular, you want to write tests for common use cases as well as possible edge cases.

One good place to look for these edge cases would be at the edge of the range of possible inputs. In some cases, it may also be worth testing how well our method handles massive inputs. To demonstrate, let’s look at a couple of examples. Suppose we were testing a method involving arrays. We would want to see how it works with empty arrays, arrays of the expected size, arrays with a single element, and perhaps very large inputs.

As another example, suppose we were working with a method involving some iteration. You would want a test with a condition causing no iteration, the expected types of cases, and something involving a single iteration. Going back to our Calculator class, let’s think about what types of tests we want for an add method.

We would want to test the case of two positive integers, two negative integers, and one positive and negative integer. Depending on how we intend to use the method, it could also be good to test how it would deal with integer overflow.

For simplicity, let’s ignore the case of integer overflow and test the other cases.

To start, we create a new class called Calculator. This class will have an add method as follows.

public class Calculator {

   public int add(int a, int b){
       return a + b;
   }

}

From here, we create a new class in our test/java folder named CalculatorTest. As the name suggests, this is where our tests will go. First, let’s make a test for when we add 2 to 2. To create our first test, we simply create a new void method with the @Test annotation:

@Test
void test1() {

}

As the name suggests, this tells JUnit to treat our method as a test. Within each test, we need to make comparisons between our expected and actual results. We do so using static methods of the org.junit.jupiter.api.Assertions class. With this in mind, we need to assert that our add method returns 4 when called with two 2’s as arguments. To do so, we use the static assertEquals method of the Assertions class as follows.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

   private Calculator calc = new Calculator();

   @Test
   void test1() {
       // Assert that our Calculator instance returns 4 when adding 2 + 2
       assertEquals(4, calc.add(2, 2));
   }

}

Note that we can call the assertEquals method directly because of the static import.

We just created our first unit test for the add method of our Calculator class. Now that you understand the basic structure of a unit test, let’s create our other tests:

@Test
void test2(){
    assertEquals(3, calc.add(4, -1));
}

@Test
void Test3(){
   assertEquals(-2, calc.add(-1, -1));
}

The BeforeEach annotation

Suppose now that we had to perform some operation on the object we are testing before each test. For example; what if we also needed to populate a custom data structure before each test? Directly repeating the same initialization in each test would reduce code maintainability.

As good practice, we want to condense repeating code into reusable chunks. To solve this problem, we can create a void method with the @BeforeEach annotation. JUnit calls methods with this annotation before each test. This is particularly useful when we want to set up each test in a particular, and more involved way.

Display names

To help increase the readability of our tests, we can change their name when they run. To do so, we use the @DisplayName annotation with the desired name as an argument. One good naming convention I’ve seen for tests is When <StateBeingTested> expect <ExpectedBehavior>. As an example, we would name the test we just made: When adding 2 and 2 expect 4.

Nested test classes

Before we begin writing tests for other calculator methods, let’s organize this test class a bit. This class will have tests for all the methods in the Calculator class. This gives it the potential to get disorganized. To improve the organization, we will use nested test classes. These are simply nested classes in our test class with the @Nested annotation. We put tests for one method in a single class.

// Set display name for nested class
@Nested
@DisplayName("Testing the add method: ")
class AdditionTests{

   // Set Display name for our test as: "When adding 2 and 2, expect 4"
   @Test
   @DisplayName("When adding 2 and 2, expect 4")
   void test1() {
       assertEquals(4, calc.add(2, 2));
   }

   @Test
   @DisplayName("When adding 4 and -1, expect 3")
   void test2(){
       assertEquals(3, calc.add(4, -1));
   }

   @Test
   @DisplayName("When adding -1 and -1, expect -2")
   void Test3(){
       assertEquals(-2, calc.add(-1, -1));
   }

}

Testing a divide method

Now let’s practice and learn more by creating tests for a divide method. Let’s first think about the types of tests we should write for this method. Thinking about edge cases, one critical test would test the case of zero division. Aside from that, we should test different combinations of sizes of divisors and dividends.

For simplicity’s sake let’s only write a test for zero division. Notice for this case we would want our divide method to throw an IllegalArgumentException. To test this behavior, we would need to use the assertThrows method of the Assertions class:

@Nested
@DisplayName("Testing the divide method: ")
class DivisionTests{

   @Test
   @DisplayName("When dividing by zero, expect an Arithmetic Exception")
   void test1() {
       // Takes the expected exception and an executable with the code we expect to throw an exception
       assertThrows(IllegalArgumentException.class, () -> calc.divide(1, 0));
   }

}

Note, the second parameter we gave to the assertThrows method was in the form of a lambda expression. In case you are not familiar with these, you can learn about it from my article on functional programming.

Introduction to Mockito

Now that we got the basics of JUnit covered, let’s see where Mockito comes into the picture. Often there are cases where we create classes that rely on the service of others. As an example, suppose we wanted a power method for our Calculator class. This method uses the help of a multiply method of a MultiplicationService class.

When we write tests for the power method, the accuracy of the multiply method could affect our results. There can be cases where it appears as if the power method is not working but it was an error in the multiply method. In which case, our tests are no longer a test of a single unit, but of two, the power and multiply method.

In cases like this, Mockito provides us a solution. Mockito is a framework used to create mock objects for the case of unit testing. It would allow us to hard-code the behavior of a fake MultiplicationService. This assures us that the multiply method will work as intended, and allows us to test the power method in isolation.

To show you how this works, let’s try implementing a test for this power method. This test will simply verify that our power method returns 4 when called with 2 and 2 as parameters. Note, we will make our Calculator class accept the MultiplicationService from its constructor. This would allow us to give it our mock object for use in the test.

To start, we need to import all the static methods of the Mockito class:

import static org.mockito.Mockito.*;

Next, we need to add the following lines of code on top of our test class.

// Create a mock object of type MultiplicationService
private final MultiplicationService multiplier = mock(MultiplicationService.class);
private final Calculator calc = new Calculator(multiplier);

Before we write our test, let’s look at the implementation of the power method to see how it uses the multiply method.

public double power(double a, double b){
   double result = 1;
   for(int i = 0; i < b; i++){
       result = multiplier.multiply(result, a);
   }
   return result;
}

The power method iterates b number of times, multiplying the result by a. Thus, it calls the multiply method 2 times, first with 1 and 2 as arguments, then 2 and 2. These method calls return 2.0 and 4.0 respectively. Thus, we need to set up our mock object to have these behaviors.

@Test
@DisplayName("When evaluating 2^2, expect 4.0")
void test1() {
   // The when and thenReturn methods come from the org.mockito package.
   when(multiplier.multiply(1, 2)).thenReturn(2.0);
   when(multiplier.multiply(2, 2)).thenReturn(4.0);
   assertEquals(4.0, calc.power(2,2));
}

As intuitively as that, we set up our mock object to return the right values that the power method needs. Note that with Mock objects we do not give a proper implementation of the multiply method. We simply hard-code the expected results for the method calls that the power method will use.

Conclusion

In this tutorial, we have gone over the basics of JUnit and Mockito. We learned how to write unit tests, how to create mock objects, and some useful tips along the way. With these new tools at your disposal, you should be able to better maintain and improve your code. This is an especially useful tool to make you a more productive programmer.

This is by no means a comprehensive guide on what there is to know about the two frameworks. To learn more, I highly recommend looking at the documentation for JUnit and Mockito. Finally, to reference all the code written here, refer to this repository.


Peer Review Contributions by: Peter Kayere


About the author

John Amiscaray

John Amiscaray is a first-year Computer Science Major at Ryerson University. He is a novice web developer trying to develop the skills to create his full-stack applications. He is very passionate about programming, viewing it as a medium for innovation. In his free time, he enjoys playing around with different technologies to try to learn new skills in a hands-on way.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.