DEV Community

Cover image for Writing unit tests in Java using jUnit and Mockito
pazvanti
pazvanti

Posted on • Edited on • Originally published at petrepopescu.tech

Writing unit tests in Java using jUnit and Mockito

Article originally posted on my personal website under Using jUnit and Mockito for unit testing in Java

Testing is important for all software systems and applications. It will help you easily find bugs and it will prevent crashes and downtimes for your application, especially if the system evolves over time. At first it may not seem critical, but as the system grows and becomes more complex, the likelihood of a bug appearing without being noticed grows.

Courses on Spring Boot and Play Framework

There are multiple testing frameworks available for Java, with jUnit being one of the most widely used. In this article, we will be looking over a few simple APIs that jUnit offers and how to use them for unit testing. In most cases, jUnit won’t be enough and the combo jUnit + Mockito is often found.

For those that don’t know, Mockito is another framework used for testing where you mock certain classes so that you can easily isolate the class you want to test and to control the output of other classes or components that are being used by the class. It also offers the possibility to check if the mock was called, how many times and more other useful features.

Validating the result in jUnit

The simples and most widely used feature in unit testing is checking that the result received is the one expected. This is a basic functionality that you will be using often when writing tests because it is mandatory to check that your method actually returns the correct answer. This is achieved using the


 methods.



```java
@Test
public void aSimpleTest() {
    int aNumber = 5;
    String str = "A test string";
    double aDouble = 4.67;

    Assert.assertEquals(5, aNumber);
    Assert.assertEquals("A test string", str);
    Assert.assertEquals(4.67004, aDouble, 0.001);
}
Enter fullscreen mode Exit fullscreen mode

A note for the assertion of the double variable. Even though the numbers are not truly equal, the test still passes. This is because the last parameter in the method represents the accuracy. This is the deviation that we allow in the result and still consider it valid. If you want better accuracy, just make the delta smaller.

Custom error message for a test

Sometimes it is important to show a custom message in case the test fails. This feature saves a lot of time during automatic builds where access to the full stack trace is hard to get. Using a custom message you can easily print what was wrong, what variable was not correct or any information that will help you (or another engineer) understand what happened. This is done by using the overloaded methods from Assert that have the message parameter first, as seen below.

@Test
public void aSimpleTestWithMessage() {
    String myStr = "This test will fail";
    Assert.assertEquals("The myStr variable is not the expected value", "Nothing", myStr);
}
Enter fullscreen mode Exit fullscreen mode

Error in jUnit

Validating results with complex objects in jUnit

In most cases, you will have complex objects that have multiple fields. It is as easy to check them as for simple ones. jUnit uses the equals() method to check that the result you received is identical to the expected one. That is why, in this case, it is important to have a properly written equals() method for your object. Let us look at this data class:

public class DataClass {
    private String field1;
    private String field2;
    private int field3;

    public DataClass(String field1, String field2, int field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        DataClass dataClass = (DataClass) o;
        return Objects.equals(field1, dataClass.field1)
                && Objects.equals(field2, dataClass.field2)
                && Objects.equals(field3, dataClass.field3);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, we can write a test and because the equals() method is properly written, the test will pass even if there are two different objects:

@Test
public void testComplexObject() {
    DataClass obj1 = new DataClass("f1Value", "f2Value", 3);
    DataClass expected = new DataClass("f1Value", "f2Value", 3);

    Assert.assertEquals(expected, obj1);
}
Enter fullscreen mode Exit fullscreen mode

All is fun, however, in certain scenarios you want to make sure that the EXACT same object is returned. This is achieved using the Assert.same() method. It works exactly like Assert.assertEquals(), however, it does not check the equally of the contents, but the actual object reference. Using it, the test will pass if and only if the expected and the result are the same.

Validating an exception is thrown in jUnit

Another useful feature is validating that the method does indeed throw a proper exception when invalid input is received. There are multiple ways of doing this, but the easies is to provide the expected parameter to the @test annotation. This will tell jUnit that the test will throw the provided exception if it is correct.

@Test(expected = NumberFormatException.class)
public void testNumberParse() {
    Integer.parseInt("a");
}
Enter fullscreen mode Exit fullscreen mode

How to mock your response using Mockito

Complex systems have multiple components and classes that interact with one-another. For such systems, it is hard to test everything at once, either because you don’t know how each and every component works, or because it will take too long to properly set up everything and run the tests. That is why the concept of mocks has been introduced. A mock is an object that replicates the API of your class, but does not do any processing. Instead, it simply returns what you tell it to.

This is useful for unit testing because it will allow you to focus on just one class (the one you actually want to test) while ignoring the inner workings of the other objects that are used by your class. As an example, let’s assume that you want to test a service and validate that the aggregation is done correct. The service uses one or multiple DAOs to retrieve the raw data from the database. You don’t want to have data written in the DB, nor you want slower tests by having to hit the DB every time.

What you do in this case is mock the DAOs and tell the mock what to return for each test. This is easily done using Mockito.

private static MyDao mockDao;

@BeforeClass
public static void setup() {
    // Initialize the mock of the type MyDao
    mockDao = Mockito.mock(MyDao.class);
}
Enter fullscreen mode Exit fullscreen mode

Now, you can write your test by setting the mocked DAO object to your service (via a setter, or using a dependency-injection mechanism) and specifying what the DAO should return when it is called. Here are our original objects. We will want to test that the service works, even though the DAO is not yet written.

public class MyService {
    private MyDao dao;

    public void setDao(MyDao dao) {
        this.dao = dao;
    }

    public boolean process(String id) {
        if (id == null) return false;

        List<DataClass> all = dao.getAll();
        if (all == null || all.isEmpty()) return false;

        DataClass ofInterest = dao.get(id);
        if (ofInterest == null) return false;

        // do some processing
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class MyDao {
    public List<DataClass> getAll() {
        return Collections.emptyList();
    }

    public DataClass get(String id) {
        return null;
    }

    public void save() {
        // do nothing
    }
}
Enter fullscreen mode Exit fullscreen mode

We will set the mock DAO as the object for our service and tell it to return proper data when called. This way we speed up development as well as test execution.

@Test
public void testService() {
    MyService service = new MyService();
    service.setDao(mockDao);

    Mockito.when(mockDao.getAll()).thenReturn(Arrays.asList(new DataClass("a", "b", 3)));
    Mockito.when(mockDao.get(Mockito.eq("a"))).thenReturn(new DataClass("a", "b", 4));

    boolean result = service.process("a");
    Assert.assertEquals(true, result);
}
Enter fullscreen mode Exit fullscreen mode

The Mockito.when() method tells the mock what to return when it is being called. Also, you can specify the parameter to expect for the methods that have parameters using the provided helper methods: Mockito.eq, Mockito.any, Mockito.matches, Mockito.same, etc.

Preparing and cleaning up test data

With complex systems, some tests become more complicated and require interaction with other systems or parts of the code to make them work properly. Either some test data needs to be written, some features or functionalities need to be turned on or simply some environment variables must be set. You can do this for each test in a suite or, you can use the Before and After features of jUnit to make sure everything is properly initialized and cleaned up.

jUnit has 4 important annotations that help with this: @Before and @BeforeClass alongside @After and @AfterClass.

The methods that are annotated with @Before are executed before each test in a class. If in a test class you have multiple methods annotated with @test, the @Before will be executed right before each test method is run. Similarly, the @After is executed after each test in a suit.

The other two annotations, @BeforeClass and @AfterClass will be executed only once, exactly before any test is run and right after all tests are finished, regardless of the test results. These methods are very useful for preparing your environment and writing any test data as well as doing the necessary cleanup in order to ensure that the environment is in a clean state with no dangling data.

Conclusions

As can be seen, Java with the help of jUnit and Mockito provides all the needed features and functionalities needed for writing unit tests for your code. Even though it may seem as an inconvenience, having well-written tests will help you identify bugs quicker as well as maintain a robust and stable software system.

As always, you can download the full sample codes on my personal website.

Article originally posted on my personal website under Using jUnit and Mockito for unit testing in Java

Top comments (0)