A Quick Start Guide to Java Unit Testing — Mockito and JUnit 5

A simple guide for junior developer who just started their journey

Jayson GCS
Javarevisited

--

Photo by me (taken at Thomson Nature Park)

Developers tend to have a love-hate relationship with unit testing. We all love that dopamine hit when we see all the green ticks ✔️ from test suite, but not everyone likes to write it.

Unit testing is a critical aspect of software engineering as it gives the following advantages to your development process:

  1. Boosts confidence in the reliability of your codebase
  2. Enforces inversion of control/dependency injection pattern
  3. Pushes developer to write good code (bad code is oftentimes difficult to test and interpret)
  4. Protects your requirements and prevents others from accidentally breaking them.
Table of Contents
— Dependencies Setup
— Sample Project
— Test Naming Convention
— Writing Unit Test
— Mocking Service Object
— Argument Captor for Mocked Object
— Mocking Public Static Method
— Testing Private Method (Reflection)
— Review Test Coverage

Dependencies Setup

To begin, we will use mockito-core, which is sufficient for mocking or partial mocking. Later, there will be examples where we will switch to using mokito-inline.

Take note that we need at least Java 8 for this sample project.

Sample Project

Let us imagine that we are building a simple message service that can send some string message via email service:

Project Structure for This Demo

The MessageService class is the main service that provides 2 methods to send the email. For security purposes, the message service has the option to send the email with or without encryption. The method which provides encryption will use a different encryption method provided by EncryptionUtils based on the input length of the message:

EmailService is a simple service which serves as an abstraction to perform email functionality. The implementation is totally not usable and is only for this simple demo purpose:

EncryptionUtils provides static methods to perform encryption for the input messages:

EmailEntity is the wrapper class that wraps the message before passing it to the EmailService instance. The implementation of the wrapper class is very simple in this example; in the real world scenario it is very common to see more complicated classes with similar use case:

Test Naming Convention

There are many common naming conventions when it comes to writing a test case; however, we will not go through all the conventions in this article. You may check them out here to see which is most suitable for your use case:

Personally, I prefer to use the format of given_when_then and name the method as comprehensively as possible, so that should the test are broken, it would takes less effort to understand what has been broken.

Writing Unit Test

Below is the basic setup for MessageServiceTest. We will use this class to demonstrate the following ideas:

  • the idea of Mock and Argument Captor
  • asserting results
  • verifying method calls

The class has an ExtendWith annotation and a specified MockitoExtension.class — this is to initialize Mockito behind the scenes. There are 3 more annotations in the class —

  • Mock: This indicates that the object will be used for stubbing the results or verifying method calls
  • Captor: This indicates that the ArgumentCaptor object is used to capture the argument values used by mocked object (in this case, by emailService)
  • BeforeEach: This indicates that the init() method will be called each time before each test starts. There are a few more JUnit5 lifecycle annotations for your reference — BeforeAll, AfterEach and AfterAll

Mocking Service Object

Mocking is basically creating a controlled environment for you to fake the response of your mocked object; you are not initializing or calling the actual object. By using the Mock annotation, we are mocking the EmailService behavior.

The test below should be easily understood given the method name:

  • given that the emailService is active
  • when we call the sendEmailMsg method
  • then result from the method call should be true
  • we also verify that while the method is called, the send method from email service should also be called once

So if we did not mock the email service, the isServiceActive() method will return false:

public boolean isServiceActive() {
return false;
}

By using Mockito.when and thenReturn, we hijacked the behavior of the mocked emailService. Moreover, we have visibility over how the mocked emailService is interacting throughout the whole method call, which allows us to verify method call for send() method in this case.

Argument Captor for Mocked Object

How do we verify that the emailEntity passing to emailService.send() via sendEmailMsg() is sending the expected message?

In this test, instead of expecting the send method to be any EmailEntity class, we swap it with our argument captor object. By doing so, after the method call is verified, we can call the getValue() method to retrieve the EmailEntity that is actually passed to the method. Therefore, we may perform further evaluation to see if the object is as expected:

What about the sendEncryptedEmailMsg() method? Let us verify that the emailEntity passing to emailService.send() is indeed sending the encrypted message:

Mocking Public Static Method

Looking back at the sendEncryptedEmailMsg() method again:

public boolean sendEncryptedEmailMsg(String message) {
if (emailService.isServiceActive()) {
String encryptedMsg;
if (message.length() < 15) {
encryptedMsg = EncryptionUtils.simpleEncrypt(message);
} else {
encryptedMsg = EncryptionUtils.notSoSimpleEncrypt(message);
}

EmailEntity emailEntity = new EmailEntity();
emailEntity.setMessage(encryptedMsg);
emailService.send(emailEntity);
return true;
} else {
return false;
}
}

Based on the message length, different encryptions will be used to process the message. But currently there is no way to mock a static method, right?

Mockito-inline provides implementation for us to mock a public static method.

Now let us take it one step further with mockito-inline dependency, it provides MockedStatic class to mock any public static method. This is only supported after version 3.4.0. In the below examples, we can finally mock the EncryptionUtils class and all its methods:

Testing Private Method (Reflection)

Although testing private method is discouraged (you should probably refactor your code if you really need to test it), there are still techniques that allow us to do so.

To test the private method, we can use Java Reflection API. It allows runtime behaviour modification on methods and objects. In order to showcase this, let us now switch to EmailServiceTest class. Here is the basic setup for MessageServiceTest:

The following test utilizes Reflection API to modify the accessor at runtime and allows us to invoke the previously private method:

Take note that Reflection will have overhead when used, so we should try not to use it in our application code.

You may read more about Java Reflection API here:

Review Test Coverage

Test coverage provides a measurable, but non-exhaustive, metrics on your codebase:

Run Test with Coverage (Intellij)
All the Test Cases are Passed ✔️
Test Coverage Results

These metrics can be incorporated into continuous integration (CI) that flags issues before or during integration process.

Mocking Private Method…?

If you have reached the point where you need to mock private method, it probably means you should consider refactoring the code. Remember to always refer to the SOLID principle. If for some reason that you are not allowed to modify the accessor or refactor the code, yet still want to mock private method and so on, do take a look at PowerMock (highly discouraged).

I have uploaded all the examples on my repository if you need some references:

If you are interested in generating TypeScript from Java Object, check out my next article:

Or perhaps you are keen to do more with less Java by utilising Lombok:

If you enjoy such stories, do consider becoming a medium member. It costs $5 per month and gives you unlimited access to Medium content. I’ll get a little commission if you sign up via link.

--

--