DEV Community

Cover image for Junit 5 Tutorial for Beginners
Sai Upadhyayula
Sai Upadhyayula

Posted on

Junit 5 Tutorial for Beginners

This post was originally published in programmingtechie.com

Junit 5 is one of the popular testing frameworks in the Java Development World and it supports the latest features of Java 8.

If you are a visual learner like me, check out the below video tutorial:

Table of Contents

Even though JUnit 5 is a successor for Junit 4, the architecture of the framework is completely different, so let’s have a look at the Architecture of Junit 5.

Junit 5 Architecture

The Architecture of Junit 5 can be divided into 3 different projects:

  • Junit Platform
  • Junit Jupiter
  • Junit Vintage

Junit Platform

The Junit Platform project provides an API to launch the tests from either the IDE’s, Build Tools or Console. It defines a TestEngine API which enables development of new Testing Frameworks on top of the Platform.

Junit Jupiter

This project provides API to write our Junit tests and extensions, it contains new annotations and a TestEngine implementation to run these tests on the platform.

Junit Vintage

This project provides a TestEngine implementation to support backward compatibility for tests written with Junit 3 and Junit4.

You can check the architecture Diagram below:

Junit 5 Architecture Diagram

Source Code for Project under Test

We are going to use a Contact Manager Application while learning how to write tests using Junit 5.

You can download the source code of the application from the below Github Link:
https://github.com/SaiUpadhyayula/contact-manager

Source Code for Starter Project

If you want to follow along this tutorial, have a look at the Starter Repository which is in the same state as the start of the tutorial.

https://github.com/SaiUpadhyayula/contact-manager-starter

The Application uses Maven to manage the dependencies and build the project. If you want to learn Maven, you can check out my Complete Maven Tutorial blog post. The application mainly contains 2 classes, Contact.java and ContactManager.java

Contact.java

public class Contact {
    private String firstName;
    private String lastName;
    private String phoneNumber;

    public Contact(String firstName, String lastName, String phoneNumber) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public void validateFirstName() {
        if (this.firstName == null)
            throw new RuntimeException("First Name Cannot be null");
    }

    public void validateLastName() {
        if (this.lastName == null)
            throw new RuntimeException("Last Name Cannot be null");
    }

    public void validatePhoneNumber() {
        if (this.phoneNumber.length() != 10) {
            throw new RuntimeException("Phone Number Should be 10 Digits Long");
        }
        if (!this.phoneNumber.matches("\\d+")) {
            throw new RuntimeException("Phone Number Contain only digits");
        }
        if (!this.phoneNumber.startsWith("0")) {
            throw new RuntimeException("Phone Number Should Start with 0");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This class just contains 3 necessary fields: First Name, Last Name and Phone Number.

ContactManager.java

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContactManager {

    Map<String, Contact> contactList = new ConcurrentHashMap<String, Contact>();

    public void addContact(String firstName, String lastName, String phoneNumber) {
        Contact contact = new Contact(firstName, lastName, phoneNumber);
        validateContact(contact);
        checkIfContactAlreadyExist(contact);
        contactList.put(generateKey(contact), contact);
    }

    public Collection<Contact> getAllContacts() {
        return contactList.values();
    }

    private void checkIfContactAlreadyExist(Contact contact) {
        if (contactList.containsKey(generateKey(contact)))
            throw new RuntimeException("Contact Already Exists");
    }

    private void validateContact(Contact contact) {
        contact.validateFirstName();
        contact.validateLastName();
        contact.validatePhoneNumber();
    }

    private String generateKey(Contact contact) {
        return String.format("%s-%s", contact.getFirstName(), contact.getLastName());
    }
}
Enter fullscreen mode Exit fullscreen mode

This class contains the main business logic to maintain contact information.

Writing our First Test

Now it is time to write our first Junit 5 Test.

I am going to create a test class called as ContactManagerTest.java under the src/test/java folder.

ContactManagerTest.java

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

public class ContactManagerTest {

    @Test
    @DisplayName("Should Create Contact")
    public void shouldCreateContact() {
        ContactManager contactManager = new ContactManager();
        contactManager.addContact("John", "Doe", "0123456789");
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }
}
Enter fullscreen mode Exit fullscreen mode
  • We created a method called shouldCreateContact() which is the method testing, the contact creation. Junit understands that this method is a Test, by looking at the @Test annotation.
  • By looking at the method name shouldCreateContact() we understand what the method is trying to do, but we can also provide a custom name for this method using the @DisplayName annotation.
  • Inside the method, we created a contact by providing the first name, last name and phone number details to the addContact() method of the ContactManager class.
  • We can retrieve all the contact information through the getAllContacts() method.
  • As a last step, we are verifying that the result of getAllContacts() is not empty, and the size of the getAllContacts() List is exactly 1.

The process of verifying the actual output with the expected output is called as performing assertions.

Junit 5 provides many different methods to perform assertions for different cases.

We have Assertions class which provides number of static methods, we can use in our tests.

If you run the above test in IntelliJ, the test will be green. Congratulations, you wrote your first Junit 5 test.

Junit 5 Intellij Test Result

You can find more details about this class in the documentation - https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html

Testing Exceptions using assertThrows()

In our first test case, we successfully tested the Happy Path.

Now let's explore some other scenarios.

Here are some validations which are performed when creating a Contact:

  • First Name should not be null
  • Last Name should not be null
  • Phone Number Should :
  • Be Exactly 10 Digits Long
  • Contain only Digits
  • Start with 0

Let's write test cases to test some of the above cases, we will cover all the scenarios in the upcoming sections.

ContactManagerTest.java

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class ContactManagerTest {

    @Test
    @DisplayName("Should Create Contact")
    public void shouldCreateContact() {
        ContactManager contactManager = new ContactManager();
        contactManager.addContact("John", "Doe", "0123456789");
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @Test
    @DisplayName("Should Not Create Contact When First Name is Null")
    public void shouldThrowRuntimeExceptionWhenFirstNameIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact(null, "Doe", "0123456789");
        });
    }
}

Enter fullscreen mode Exit fullscreen mode
  • The 2nd Test Case, tests whether the Contact Creation is Failed when we enter the First Name as Null.
  • We are asserting that the addContact() method throws a RuntimeException
  • We can assert the Exceptions using the assertThrow() method from the Assertions class
  • The assertThrow() method takes the Exception Type as the first parameter and the Executable which is throws the Exception as the second parameter.

We can write similar test cases also for the fields Last Name and Phone Number like below:

ContactManagerTest.java

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class ContactManagerTest {

    @Test
    @DisplayName("Should Create Contact")
    public void shouldCreateContact() {
        ContactManager contactManager = new ContactManager();
        contactManager.addContact("John", "Doe", "0123456789");
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @Test
    @DisplayName("Should Not Create Contact When First Name is Null")
    public void shouldThrowRuntimeExceptionWhenFirstNameIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact(null, "Doe", "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Last Name is Null")
    public void shouldThrowRuntimeExceptionWhenLastNameIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", null, "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Phone Number is Null")
    public void shouldThrowRuntimeExceptionWhenPhoneNumberIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", "Doe", null);
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

You can observe that I added similar test cases for the Phone Number and Last Name fields as well.

You should see the below output when running the tests:
Junit5 Test Results IntelliJ

Understanding Test Lifecycle

Now let's go ahead and understand the Lifecycle of Junit Tests. Each Test undergoes different phases as part of it's execution, each phase is represented by an Annotation.

  • @BeforeAll

The method marked with this annotation will be executed before any of the @Test methods are executed inside the Test class.

  • @BeforeEach

The method marked with this annotation will be executed before each @Test method in the Test class.

  • @AfterEach

The method marked with this annotation will be executed after each @Test method in the Test class.

  • @AfterAll

The method marked with this annotation will be executed after all the @Test methods are executed in the Test class.

The Before methods (@BeforeAll, @BeforeEach) perform some initialization actions like setting up of test data or environment data, before executing the tests.

The After methods (@AfterAll, @AfterEach) perform clean up actions like cleaning up of created Environment Data or Test Data.

@BeforeAll and @AfterAll are called only once for the entire test, and the methods are usually marked as static.

Junit 5 Test Lifecycle

Implementing Lifecycle Annotations in our Test

So now, let’s see how we can use the above mentioned Lifecycle annotations, in our test.

In our 4 tests, we can observe that we are creating an instance of ContactManager at the start of each test.

This logic can go inside the method which is marked with either @BeforeAll or @BeforeEach

@BeforeAll and @AfterAll

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class ContactManagerTest {

    private static ContactManager contactManager;

    @BeforeAll
    public static void setup() {
        System.out.println("Instantiating Contact Manager before the Test Execution");
        contactManager = new ContactManager();
    }

    @Test
    @DisplayName("Should Create Contact")
    public void shouldCreateContact() {
        contactManager.addContact("John", "Doe", "0123456789");
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @Test
    @DisplayName("Should Not Create Contact When First Name is Null")
    public void shouldThrowRuntimeExceptionWhenFirstNameIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact(null, "Doe", "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Last Name is Null")
    public void shouldThrowRuntimeExceptionWhenLastNameIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", null, "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Phone Number is Null")
    public void shouldThrowRuntimeExceptionWhenPhoneNumberIsNull() {
        ContactManager contactManager = new ContactManager();
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", "Doe", null);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

I created a method called as setup() which is marked with @BeforeAll annotation, inside this method, I instantiated the ContactManager and assigned the object to the static variable.

I also added a method called as tearDown() which is marked with @AfterAll annotation, we expect this method to be executed at the end of the test.

If you run the tests again, you should see the output like you see in the below image:

Test Execution With BeforeAll and AfterAll

@BeforeEach and @AfterEach

By using this annotation, we can perform some operations which needs to be done Before and After Each Test Execution.

Note that, Junit always creates a new instance of the test before executing each @Test method.

import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class ContactManagerTest {

    private ContactManager contactManager;

    @BeforeAll
    public static void setupAll() {
        System.out.println("Should Print Before All Tests");
    }

    @BeforeEach
    public void setup() {
        System.out.println("Instantiating Contact Manager");
        contactManager = new ContactManager();
    }

    @Test
    @DisplayName("Should Create Contact")
    public void shouldCreateContact() {
        contactManager.addContact("John", "Doe", "0123456789");
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @Test
    @DisplayName("Should Not Create Contact When First Name is Null")
    public void shouldThrowRuntimeExceptionWhenFirstNameIsNull() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact(null, "Doe", "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Last Name is Null")
    public void shouldThrowRuntimeExceptionWhenLastNameIsNull() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", null, "0123456789");
        });
    }

    @Test
    @DisplayName("Should Not Create Contact When Phone Number is Null")
    public void shouldThrowRuntimeExceptionWhenPhoneNumberIsNull() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            contactManager.addContact("John", "Doe", null);
        });
    }

    @AfterEach
    public void tearDown() {
        System.out.println("Should Execute After Each Test");
    }

    @AfterAll
    public static void tearDownAll() {
        System.out.println("Should be executed at the end of the Test");
    }
}
Enter fullscreen mode Exit fullscreen mode

I moved the logic to create the ContactManager object inside the setup() method marked with @BeforeEach and I renamed the method marked with @BeforeAll to setupAll()

I also introduced a new method called as tearDown() which is just logging some random text, just to show you that the method is executing after each test execution.

If you run the above test, this is how the output should look like:

Test Execution Junit 5 BeforeEach and AfterEach

Default Test Instance Lifecycle

As mentioned above, Junit instantiates the Test class for each method marked with @Test.

For this reason, the method marked with @BeforeAll and @AfterAll should be marked with static.

Changing Default Test Instance Lifecycle

You can change this default behavior, by instructing Junit to create an instance of the Test class, only once using the below annotation.

@TestInstance(Lifecylce.PER_CLASS)

By using the above annotation, there is no need to mark the @BeforeAll and @AfterAll methods as static.

Conditional Executions

We can execute the @Test methods in our class based on a specific condition, for example: imagine if you developed a specific functionality on Linux for our Contact Manager application. After saving the contact, we are performing some additional logic which is specific to Linux Operating System.

Then we have to make sure that the test should only run when it’s running only on Linux Operating System.

You can enable this conditional execution using different annotations:

  • @EnabledOnOs
  • @DisabledOnOs

These annotations take the values for usual Operating Systems like MAC, LINUX, WINDOWS.

You can see the example usage of this annotation below:

@Test
@DisplayName("Should Create Contact")
@EnabledOnOs(value = OS.MAC, disabledReason = "Should Run only on MAC")
public void shouldCreateContact() {
    contactManager.addContact("John", "Doe", "0123456789");
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

We can also provide a reason using the disabledReason field, you can see the output in the below image:

Junit 5 Disabled Tests

Assumptions

We can also perform conditional Assertions using Junit 5 Assumptions.

There may be particular test cases you want to make sure that you are running based on certain conditions.

You can have some test cases which should be executed only on Developer Machine but not on CI environment.

Or you can also have some slow running tests, you can enable on disable them based on certain conditions.

You can perform this assumptions in Junit 5 similar to the Assertions.

@Test
@DisplayName("Test Contact Creation on Developer Machine")
public void shouldTestContactCreationOnDEV() {
    Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")));
    contactManager.addContact("John", "Doe", "0123456789");
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode
  • In the above test, you can see that we are using the Assumptions.assumeTrue() method to execute the tests, only when the ENV System Property is equal to DEV.
  • If the above assumption failed, then the test will not be executed.

Here is the output when the assumptions is failed:

Assumptions

Now let’s go ahead and add the System Property ENV to the VM Options of the ContactManagerTest class, you can do that by clicking on the Edit Configuration options for the Test inside IntelliJ.

Edit Configuration for Test

Once you run the test after activating this property, you can see that the test is executed successfully.

Test Result for Assumptions

Repeated Tests

If you have any functionality which has some randomness to it, you may want to create and run that test multiple times, to make sure that the functionality is working as expected.

Junit 5 provides us the @RepeatedTest annotation to fulfil this requirement.

You can repeat the Test N number of time by passing the number as an argument to the annotation.Eg: @RepeatedTest(5) will run the test marked with this annotation 5 times.

@DisplayName("Repeat Contact Creation Test 5 Times")
@RepeatedTest(5)
public void shouldTestContactCreationRepeatedly() {
    contactManager.addContact("John", "Doe", "0123456789");
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

If you run the above test, you can see the below output:

Repeated Tests

You can create that IntelliJ displays an expandable node where you can see the output for each repetition.

You can also specify custom names instead of the default repetition 1 of 5, using the name field.

Eg:
@RepeatedTest(value = 5,
name = "Repeating Contact Creation Test {currentRepetition} of {totalRepetitions}")

@DisplayName("Repeat Contact Creation Test 5 Times")
@RepeatedTest(value = 5,
        name = "Repeating Contact Creation Test {currentRepetition} of {totalRepetitions}")
public void shouldTestContactCreationRepeatedly() {
    contactManager.addContact("John", "Doe", "0123456789");
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

When you run the above test, you can see the below output:

Junit 5 Repeating Tests

Parameterized Tests

You can also run parametrized tests by running a test multiple times and providing different set of inputs for each repetition.

We have to add the @ParameterizedTest annotation to mark a test method as Parameterized Test.

@Test
@DisplayName("Phone Number should start with 0")
public void shouldTestPhoneNumberFormat() {
    contactManager.addContact("John", "Doe", "0123456789");
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

In the above testcase, we are checking whether the provided phone number is in the required format or not. i.e.. If the Phone Number is starting with 0

We can run this test against different set’s of input and make sure that the test is working as expected or not.

You can provide the input to the test using different ways:

ValueSource

You can provide input to the Parameterized Test using the @ValueSource annotation where you can provide a set of string, long, double, float literals to our test.

Eg: @ValueSource(strings = {“string1″,”string2″,”string3”})

Then you can access this string inside the test by first adding a String parameter to our test.

@DisplayName("Phone Number should match the required Format")
@ParameterizedTest
@ValueSource(strings = {"0123456789", "1234567890", "+0123456789"})
public void shouldTestPhoneNumberFormat(String phoneNumber) {
    contactManager.addContact("John", "Doe", phoneNumber);
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

In the above example we are providing the Phone Number String in different formats.

If you run the above test, it provides the below output

Value Source

The Test verifies whether the Phone Number starts with 0 or not, as we provided invalid inputs for the 2nd and 3rd cases, the tests failed.

MethodSource

We can also use @MethodSource annotation to provide the input to our Parameterized Tests, using this annotation we will refer the method name which returns the values required for our tests as output.

@DisplayName("Method Source Case - Phone Number should match the required Format")
@ParameterizedTest
@MethodSource("phoneNumberList")
public void shouldTestPhoneNumberFormatUsingMethodSource(String phoneNumber) {
    contactManager.addContact("John", "Doe", phoneNumber);
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}

private List<String> phoneNumberList() {
    return Arrays.asList("0123456789", "1234567890", "+0123456789");
}
Enter fullscreen mode Exit fullscreen mode

In the above test, we declared a method called as phoneNumberList() which returns the required input values as a List<String>.

We passed the name of this method as the value to the @MethodSource annotation.

You can see the below output when you run the test.

Method Source

CsvSource

You can create an inline CSV which contains the input for the test and reference that using the @CsvSource annotation.

Ex:
@CsvSource({"1,0123456789", "2,1234567890","3,+0123456789"})

@DisplayName("CSV Source Case - Phone Number should match the required Format")
@ParameterizedTest
@CsvSource({"0123456789", "1234567890","+0123456789"})
public void shouldTestPhoneNumberFormatUsingCSVSource(String phoneNumber) {
    contactManager.addContact("John", "Doe", phoneNumber);
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

CsvFileSource

You can also reference a CSV File by using the @CsvFileSourceannotation.

Eg:
@CsvFileSource(resources = "/data.csv")

@DisplayName("CSV File Source Case - Phone Number should match the required Format")
@ParameterizedTest
@CsvFileSource(resources = "/data.csv")
public void shouldTestPhoneNumberFormatUsingCSVFileSource(String phoneNumber) {
    contactManager.addContact("John", "Doe", phoneNumber);
    assertFalse(contactManager.getAllContacts().isEmpty());
    assertEquals(1, contactManager.getAllContacts().size());
}
Enter fullscreen mode Exit fullscreen mode

The contents of the csv looks like below:

0123456789
1234567890
+0123456789
Enter fullscreen mode Exit fullscreen mode

Here is how the expected output looks like:

CSV File Source

Disabled Tests

You can disable some tests from running by adding the @Disabled annotation.

@Test
@DisplayName("Test Should Be Disabled")
@Disabled
public void shouldBeDisabled() {
    throw new RuntimeException("Test Should Not be executed");
}
Enter fullscreen mode Exit fullscreen mode

If you run the above test, you can see the output from Junit 5 that the test shouldBeDisabled() is @Disabled

Disabled Test

Nested Tests

By now, you may have observed that our ContactManagerTest contains lots of test methods, we can organize these tests into Nested Tests using the @Nested annotation.

We are going to take a group of Tests and organize them into a seperate category of tests by creating a class and adding the @Nested annotation to it.

@Nested
class ParameterizedTests {
    @DisplayName("Phone Number should match the required Format")
    @ParameterizedTest
    @ValueSource(strings = {"0123456789", "1234567890", "+0123456789"})
    public void shouldTestPhoneNumberFormatUsingValueSource(String phoneNumber) {
        contactManager.addContact("John", "Doe", phoneNumber);
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @DisplayName("CSV Source Case - Phone Number should match the required Format")
    @ParameterizedTest
    @CsvSource({"0123456789", "1234567890", "+0123456789"})
    public void shouldTestPhoneNumberFormatUsingCSVSource(String phoneNumber) {
        contactManager.addContact("John", "Doe", phoneNumber);
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }

    @DisplayName("CSV File Source Case - Phone Number should match the required Format")
    @ParameterizedTest
    @CsvFileSource(resources = "/data.csv")
    public void shouldTestPhoneNumberFormatUsingCSVFileSource(String phoneNumber) {
        contactManager.addContact("John", "Doe", phoneNumber);
        assertFalse(contactManager.getAllContacts().isEmpty());
        assertEquals(1, contactManager.getAllContacts().size());
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, you can see that we grouped all the Parameterized Tests together into a separate class.

If I run the tests you will see that the parameterized tests are displayed under a different node.

Nested Tests

Running Tests using Maven Surefire Plugin

Usually, you will run the tests when building the application, on a CI server. Maven is a build tool which helps us in automating the manual tasks associated with building the project.

We can use the Maven Surefire Plugin to execute the tests as part of the build, all you have to do is configure the plugin in the pom.xml file.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
</plugin>
Enter fullscreen mode Exit fullscreen mode

You should also add the Maven Compiler Plugin to compile our test classes as part of the build.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <!--Use this only when using Java 9+-->
        <release>15</release>
        <!--
        Uncomment this and comment out <release> when using Java 8
        <source>1.8</source>
        <target>1.8</target>
        -->
    </configuration>
</plugin>
Enter fullscreen mode Exit fullscreen mode

The configuration section compiles the test classes according to the target JDK version.

If you are using Java 9+ you should just use the release tag and if you are using Java 8, then you should uncomment the source and target section.

After adding these plugins, you can run the tests using the command mvn clean test

λ mvn clean test
 [INFO] Scanning for projects…
 [INFO]
 [INFO] --------------------< org.example:contact-manager >---------------------
 [INFO] Building contact-manager 1.0-SNAPSHOT
 [INFO] --------------------------------[ jar ]---------------------------------
 [INFO]
 [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ contact-manager ---
 [INFO] Deleting F:\contact-manager\target
 [INFO]
 [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ contact-manager ---
 [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
 [INFO] Copying 0 resource
 [INFO]
 [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ contact-manager ---
 [INFO] Changes detected - recompiling the module!
 [WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
 [INFO] Compiling 2 source files to F:\contact-manager\target\classes
 [INFO]
 [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ contact-manager ---
 [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
 [INFO] Copying 1 resource
 [INFO]
 [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ contact-manager ---
 [INFO] Changes detected - recompiling the module!
 [WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
 [INFO] Compiling 1 source file to F:\contact-manager\target\test-classes
 [INFO]
 [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ contact-manager ---
 [INFO]
 [INFO] -------------------------------------------------------
 [INFO]  T E S T S
 [INFO] -------------------------------------------------------
 [INFO] Running ContactManagerTest
 Should Print Before All Tests
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Instantiating Contact Manager
 Should Execute After Each Test
 Should be executed at the end of the Test
 [WARNING] Tests run: 24, Failures: 0, Errors: 0, Skipped: 3, Time elapsed: 0.215 s - in ContactManagerTest
 [INFO]
 [INFO] Results:
 [INFO]
 [WARNING] Tests run: 24, Failures: 0, Errors: 0, Skipped: 3
 [INFO]
 [INFO] ------------------------------------------------------------------------
 [INFO] BUILD SUCCESS
 [INFO] ------------------------------------------------------------------------
 [INFO] Total time:  2.813 s
 [INFO] Finished at: 2020-12-26T00:41:57+01:00
 [INFO] ------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Conclusion

So we reached to the end of the Junit 5 Complete Tutorial, I hope you learned something about Junit 5.

Please don’t forget to share this article with your friends and colleagues if they find it useful.

I will see you in the next article, until then Happy Coding Techies 🙂

Discussion (1)

Collapse
mohdfarhankazi profile image
mohdfarhankazi

Post is very good and informative. I spent considerable time and practiced on its examples. Thanks. But I found some mistakes in coding examples snippets. I am listing it in following.

1) Implementing Lifecycle Annotations in our Test
@BeforeAll and @AfterAll

In this section you are creating instance of ContactManager in 3 test methods which are shouldThrowRuntimeExceptionWhenFirstNameIsNull(), shouldThrowRuntimeExceptionWhenLastNameIsNull and shouldThrowRuntimeExceptionWhenPhoneNumberIsNull. This is not required because You already created instance
in setup method marked with @BeforeAll annotation

2) Parameterized Tests

You gave example of Paramterized test for method source using annotation @MethodSource. Here you using instannce method which name phoneNumberList. I think it should be static. I tried with instance but would fail untill I did not make it static.

Again Thanks for such great tutorial.