Junit5 Parametrized Tests

 What are parameterized unit tests and How to write them using Junit5?

In this article we will learn about parameterized tests and how to write Junit parameterized tests. The parameterized tests is the concept of running a single test multiple times, but with different parameters and outcomes. The parameterized unit tests allow us to avoid writing same code multiple times.

JUnit 5, the newest version of JUnit, adds a slew of new capabilities to make creating developer tests easier.

Parameterized tests are one such feature. This feature allows us to repeat a test procedure with varied parameters many times.

To start writing parameterized tests, we first need to include `junit-jupiter-params` dependency in our project.

For Maven, add below dependency to the project's pom.xml file.


<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

Let's consider the following class, with a utility method to verify if an integer is odd number or not.

IntegerUtils.java

public final class IntegerUtils {

    public static boolean isOdd(int number) {
        return number % 2 != 0;
    }
}

We can write a simple unit test that will invoke the `isOdd()` method of the util class with various number of options and verify it's working.

Let's consider the following class, with a utility method to verify if an integer is odd number or not.

IntegerUtilsTest.java

class IntegerUtilsTest {

    @ParameterizedTest
    @ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
    void test_odd_numbers(int value) {
        Assertions.assertTrue(IntegerUtils.isOdd(value));
    }

    @ParameterizedTest
    @ValueSource(ints = {0, 2, 1000, -30, 150, (Integer.MAX_VALUE - 1)})
    void test_even_numbers(int value) {
        Assertions.assertFalse(IntegerUtils.isOdd(value));
    }
}

Above is a very simple example of how parameterization works in Junit5 framework.  In further articles, we will explore different variations of parameterized unit tests supported by Junit5 framework.

Full source code can be found in our Github Repository.


Test Callback Invocation using Junit5 and Mockito

In this article we will use thenAnswer method of Mockito to invoke a callback method passed to a mock. 

Let's consider following classes.

Here, ServiceToBeTested class is calling submitCallback() method of ServiceToBeMocked class and passing it a Callback object.

The value set in Callback object is latter returned by the method being tested.

ServiceToBeTested.java

public class ServiceToBeTested {

    private final ServiceToBeMocked serviceToBeMocked;

    public ServiceToBeTested(ServiceToBeMocked serviceToBeMocked) {
        this.serviceToBeMocked = serviceToBeMocked;
    }

    public String methodToBeTested() {
        Callback callback = new Callback();
        serviceToBeMocked.submitCallback(callback);

        return "Value Received after callback = " + callback.getValue();
    }
}
ServiceToBeMocked.java

public class ServiceToBeMocked {

    public void submitCallback(Callback callback) {
        //some logic that sets value back in callback.
        callback.setValue(null);
    }
}
Callback.java

/**
 * Simple class demonstrating callback events. The implementation could be much more complex.
 */
public class Callback {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

In above scenario, thenReturn method of Mockito will be of no use. We can use thenAnswer() method in this case to achieve our test scenario.

ServiceToBeTestedTest.java

@ExtendWith(MockitoExtension.class)
class ServiceToBeTestedTest {

    @Mock
    private ServiceToBeMocked serviceToBeMocked;

    @InjectMocks
    private ServiceToBeTested serviceToBeTested;

    @Test
    void test_callback() {
        Mockito.doAnswer(it -> {
            Callback callback = it.getArgument(0);
            callback.setValue("Mocked Value");
            return null;
        }).when(serviceToBeMocked).submitCallback(Mockito.any(Callback.class));

        String returnValue = serviceToBeTested.methodToBeTested();

        Assertions.assertEquals("Value Received after callback = Mocked Value", returnValue);
    }
}

Full sample code can be found at our Github repository.

How to verify values passed to a mocked method?

 In this tutorial we will learn how we can verify values passed to a method.

Many times we want to verify values passed to a method while execution of the method being tested. This situation may arise when invoked method has no effect on the calling method. Eg. Publishing an event.

Let's take a look at the following example, where the `updateBalance()` method is calling `NotificationService` method to publish an event.

AccountService.java

public class AccountService {
    private final NotificationService notificationService;

    public void updateBalance(String accountNumber, BigDecimal newBalance) {
        // some calculations
        notificationService.publish(new AccountUpdatedEvent(accountNumber, newBalance));
    }
}


NotificationService.java

public class NotificationService {

    /**
     * Publish an event to the message broker
     *
     * @param event
     */
    public void publish(Event event) {
        // logic here to write to message broker
    }
}

Now if we want to test that an AccountUpdateEvent, containing correct account number and balance is being passed to the NotificationService, we can use ArgumentCaptor.

AccountServiceTest.java

@ExtendWith(MockitoExtension.class)
class AccountServiceTest {

    @Captor
    private ArgumentCaptor<AccountUpdatedEvent> accountUpdatedEventCaptor;

    @Mock
    private NotificationService notificationService;

    @InjectMocks
    private AccountService accountService;

    @Test
    void verify_update_event() {
        final String accountNumber = "636636636";
        final BigDecimal newBalance = BigDecimal.valueOf(1000000L);

        accountService.updateBalance(accountNumber, newBalance);

        Mockito.verify(notificationService).publish(accountUpdatedEventCaptor.capture());

        AccountUpdatedEvent accountUpdatedEvent = accountUpdatedEventCaptor.getValue();
        Assertions.assertEquals(accountNumber, accountUpdatedEvent.getAccountNumber());
        Assertions.assertEquals(newBalance, accountUpdatedEvent.getBalance());
    }

}


Full source code can be found on Github.


Setup Lombok with Spring Boot

 Lombok is a Java library that takes care of generating lot of this boiler plate code for the developers.

Lombok works by plugging into your build process and hooking into Java byte-code generation stage, to add required code based on your specifications. Lombok uses annotations to identify which piece of code needs to be auto generated.

Adding Lombok to any project is very simple. You simply have to include the  Lombok package in your build path. 

If you are using Spring boot, Lombok version will be resolved for you by Spring.

Maven pom.xml

    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.3.12.RELEASE</version>
        <relativepath>
    </relativepath></parent>

    <dependencies>
        <!--https://mvnrepository.com/artifact/org.projectlombok/lombok-->
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Lombok Sample Person.java


@Data
public class Person {
    private String name;
    private int age;
}

Unit Test PersonTest.java


class PersonTest {

    @Test
    public void data_annotation() {
        Person person = new Person();
        person.setName("David Hoff");
        person.setAge(25);

        Assertions.assertEquals("David Hoff", person.getName());
        Assertions.assertEquals(25, person.getAge());
    }
}

The entire sample code can be found on Github