the/experts. Blog

Cover image for Mastering Void Method Testing with Mockito & JUnit 5 in Spring Boot Applications
Maik Kingma
Maik Kingma

Posted on

Mastering Void Method Testing with Mockito & JUnit 5 in Spring Boot Applications

Testing is an essential part of any software development process. Mockito, a powerful mocking framework, can help you test different parts of your application, including void methods. Void methods can be challenging to test, as the focus is on the program flow rather than the output. In this blog post, we will explore how to effectively test void methods using Mockito in a Spring Boot application.

Getting Started: Setting Up Dependencies

To begin, ensure that you have the necessary dependencies in your project. Add the following dependencies to your pom.xml or build.gradle file:

For Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

For Gradle:

testImplementation 'org.springframework.boot:spring-boot-starter-test'
Enter fullscreen mode Exit fullscreen mode

These dependencies will bring in Mockito and other necessary testing libraries. For a complete project setup, feel free to use the prepared Spring initializr

Testing Void Methods

Let's consider a simple example: a Spring Boot application with a service that sends email notifications. The EmailService class has a sendEmail method, which is void and sends an email to a given recipient.

@Service
public class EmailService {

    private final EmailClient emailClient;

    public EmailService(EmailClient emailClient) {
        this.emailClient = emailClient;
    }

    public void sendEmail(String recipient, String subject, String content) {
        emailClient.send(recipient, subject, content);
    }
}
Enter fullscreen mode Exit fullscreen mode
public interface EmailClient {
    void send(String recipient, String subject, String content);
}
Enter fullscreen mode Exit fullscreen mode

To test the sendEmail method, we will use Mockito to verify the program flow of the method execution.

1. Create a test class

First, create a test class named EmailServiceTest:

@ExtendWith(MockitoExtension.class)
public class EmailServiceTest {

    private EmailService emailService;

    // Test cases will be added here
}
Enter fullscreen mode Exit fullscreen mode

2. Mock the dependencies

Next, we need to mock any dependencies or external systems the EmailService interacts with, in this case, an EmailClient:

Mock the EmailClient in the test class:

@ExtendWith(MockitoExtension.class)
class EmailServiceTest {

    @Mock
    private EmailClient emailClient;

    @InjectMocks
    private EmailService emailService;
}
Enter fullscreen mode Exit fullscreen mode

3. Test the void method

Now, we can create a test case for the sendEmail method. We will use Mockito's verify() method to ensure that the send() method of the EmailClient is called with the correct parameters.

@ExtendWith(MockitoExtension.class)
class EmailServiceTest {

    @Mock
    private EmailClient emailClient;

    @InjectMocks
    private EmailService emailService;

    @Test
    void sendEmail() {
        // given
        String recipient = "test@example.com";
        String subject = "Test Subject";
        String content = "Test Content";
        // when
        emailService.sendEmail(recipient, subject, content);
        // then
        verify(emailClient, times(1)).send(recipient, subject, content);
    }
}
Enter fullscreen mode Exit fullscreen mode

Testing Exceptions with assertThrows()

In some cases, (void) methods may throw exceptions when specific conditions are met. It is essential to test these scenarios to ensure the application's resilience and robustness. In this section, we will cover how to test (void) methods that throw exceptions using JUnit 5's assertThrows() or AssertJ's assertThatThrownBy() functions.

Continuing with our EmailService example, let's assume that the sendEmail method throws an InvalidEmailException when the recipient's email address is invalid.

@Service
public class EmailService {

    public void sendEmail(String recipient, String subject, String content) throws InvalidEmailException {
        // Implementation for sending an email
    }
}
Enter fullscreen mode Exit fullscreen mode

To test the scenario where the method throws an InvalidEmailException, we will follow these steps:

1. Update the EmailClient interface to declare the exception:

public interface EmailClient {
    void send(String recipient, String subject, String content) throws InvalidEmailException;
}
Enter fullscreen mode Exit fullscreen mode

2. Configure the mock to throw the exception:

In the test class, use Mockito's doThrow() method to configure the emailClient mock to throw an InvalidEmailException when the send() method is called with an invalid email address.

@Test
public void testSendEmailThrowsInvalidEmailException() {
    String invalidRecipient = "invalid-email";
    String subject = "Test Subject";
    String content = "Test Content";

    doThrow(InvalidEmailException.class).when(emailClient).send(eq(invalidRecipient), anyString(), anyString());

    // Use JUnit's assertThrows() to test the exception
    assertThrows(InvalidEmailException.class, () -> emailService.sendEmail(invalidRecipient, subject, content));
}
Enter fullscreen mode Exit fullscreen mode

In this test case, we use JUnit 5's assertThrows() method to test that the InvalidEmailException is thrown when calling the sendEmail method with the invalid email address.

If you prefer to test with AssertJ, here is an example that makes use of AssertJ's assertThatThrownBy() function to essentially test the same thing as we did above:

@Test
public void testSendEmailThrowsInvalidEmailException() {
    String invalidRecipient = "invalid-email";
    String subject = "Test Subject";
    String content = "Test Content";

    doThrow(InvalidEmailException.class).when(emailClient).send(eq(invalidRecipient), anyString(), anyString());

    // Use AssertJ's assertThatThrownBy() to test the exception
    assertThatThrownBy(() -> emailService.sendEmail(invalidRecipient, subject, content))
        .isInstanceOf(InvalidEmailException.class);
}
Enter fullscreen mode Exit fullscreen mode

Back in JUnit 4, there was another approach to testing exception throwing of methods, namely

@Test(expected = InvalidEmailException.class)
public void testSendEmailThrowsInvalidEmailException() throws InvalidEmailException {
    // Some test that will throw the exception
}
Enter fullscreen mode Exit fullscreen mode

Both assertThrows() from JUnit 5 and assertThatThrownBy() from AssertJ are more modern and straightforward compared to the expected attribute of the @Test annotation from JUnit 4 for the following reasons:

  1. Explicitness: Both assertThrows() and assertThatThrownBy() make it clear which part of the code is expected to throw the exception. In contrast, the expected attribute of the @Test annotation only specifies that the test method is expected to throw an exception, but it doesn't pinpoint the exact line of code where the exception is expected.

  2. Exception capturing: Both assertThrows() and assertThatThrownBy() allow you to capture the thrown exception, enabling you to perform additional assertions on the exception itself, such as checking the exception message, cause, or other properties. In comparison, the expected attribute doesn't provide a way to capture the exception for further assertions.

  3. Readability: Both assertThrows() and assertThatThrownBy() improve the readability of the test code by making the exception testing more explicit and more focused on the specific code block that is expected to throw an exception. This enhances the test's clarity and makes it easier for developers to understand the test's purpose.

Conclusion

Testing void methods' general behavior and testing methods that throw exceptions is crucial for ensuring the reliability and robustness of an application. By using Mockito to configure mocks and AssertJ's or JUnit5's assert methods to verify exceptions, we can create comprehensive test cases that cover various scenarios, including those involving exceptions. As a result, we can develop more reliable and resilient code.

Discussion (0)