The Ultimate Guide to JUnit 5 Annotations | Learn Java Skills

Imran Shaikh
0

JUnit is the most widely used unit testing framework in Java, and JUnit 5 is the most recent version of it.


You're probably thinking if it makes sense to write code to test your code.


It definitely is. Let's use a vehicle as an example. You will never buy a vehicle that has never been tested before.


What do you think?


Just like a car, you must test your code to check how it behaves or functions before releasing it to the production.


This instructional article will cover a variety of frequently utilized JUnit 5 annotations, and it will also cover hands-on work with the JUnit 5 annotations.


toc

Why should you learn JUnit 5 rather than the previous version of JUnit?


JUnit 5 is the most recent version of JUnit, and it's filled with new features and enhancements over the previous version.


JUnit5 contains a new feature and an extensible architecture, as well as support for new unit testing styles and new annotations.


Thumbnail image of the The Ultimate Guide to JUnit 5 Annotations

JUnit5's Architecture


There are three main modules in JUnit 5, and they are as follows:


JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit 5 architecture image

Junit Platform

The JUnit platform module is in charge of starting the testing framework on the Java Virtual Machine (JVM).


The JUnit platform additionally provides the TestEngine API, which is used to create a unit testing framework that can be executed on the JUnit platform. By enhancing your own custom TestEngine, you may integrate or plug in a third-party testing library into JUnit 5.


Junit Jupiter

In order to enhance the new programming style, JUnit Jupiter was established, and it is provided with the new extension model to write unit tests in JUnit 5.


In comparison to the previous version, additional annotations have been included, including but not limited to @DisplayName , @BeforeEach , @AterAll , @ExtendsWith , @Disable , @AfteEach , and @BeforeAll.


JUnit Vintage

The JUnit Vintage module adds compatibility in JUnit 5 for unit test cases created in a previously JUnit version, such as JUnit 4 or JUnit 3.


Dependencies


To get started with JUnit 5, add the required dependencies to your Java project. You just need one dependency, which is JUnit Jupiter.


Gradle with Groovy build tool

If you're using the Gradle with Groovy build tool, include the Junit Jupiter dependencies  in your build.gradle file.


testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'(code-box)

Maven build tool

If you are using the Maven build tool, then add the dependencies listed below, such as Junit Jupiter, to work with JUni 5.


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

As always, the most recent version is available through the Maven Repository


JUnit 5 Annotations Guide

Let's now get into the numerous JUnit 5 annotations that are often utilized. Not only will we talk about annotations, but we will also learn how to utilize them in our project.


@Test annotation

The @Test annotation indicates that a method is the test method, but it does not specify any attributes.


The @Test annotation is included in the import org.junit.jupiter.api.Test; package.


 package in.learnjavaskills;

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

 class CalculatorTest
 {

    @Test
    void testAdditionOfTwoNumber()
    {
        Calculator calculator = new Calculator();
        int sum = calculator.add(10, 10);
        assertEquals(20, sum);
    }
 }

@DisplayName annotation

The @DisplayName annotation is used to specify a unique name for the test method or class.


The @DisplayName allows a single attribute, i.e., value, to name the test case and is found in the package import org.junit.jupiter.api.DisplayName;.


 package in.learnjavaskills;

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

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

 @DisplayName("Calculator service test")
 class CalculatorTest
 {

    @Test
    @DisplayName("testing addition of two method")
    void testAdditionOfTwoNumber()
    {
        Calculator calculator = new Calculator();
        int sum = calculator.add(10, 10);
        assertEquals(20, sum);
    }
 }

If you do not give a display name for the class or method, it will use the default class name and test method name, as seen in the screenshot below.


running test case with out using the @DisplayName annotation

Let's repeat the test with the @DisplayName annotation. This time, we can see the correct names of the test cases, which we specified using the @DisplayName annotation.


unit test case when using the @DisplayName annotation

@ParameterizedTest annotation

The @ParameterizedTest annotation, like the @Test annotation, is used at the method level to indicate that the method is a test method.


The primary difference between the two annotations is that @ParameterizedTest allows us to execute the test method numerous times with varied parameters, allowing us to evaluate the code's behavior by supplying new input each time.


If you use the @ParameterizedTest , you must also use the @ValueSource since the parameterized test method requires at least one source of input.


 package in.learnjavaskills;

 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;

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

 class NumbersTest
 {
    @ParameterizedTest
    @ValueSource(ints = {2, 4, 6, 8, 10})
    void testEvenNumber(int input)
    {
        Numbers numbers = new Numbers();
        boolean evenNumber = numbers.isEvenNumber(input);
        assertEquals(evenNumber, true);
    }
 } 

When you look back into the IDE console, you will find that same test method has been executed five times with different argument input.


@ParameterizedTest annoatation scrren shot of IDE

The @ValueSource argument, ints = 2, 4, 6, 8, 10, will be sent into the testEvenNumber method's parameter, i.e., int input.(alert-success)

Different data types can be used in the @ValueSources

@ValueSource parameter Data Type
strings(code-box)
String
ints(code-box)
int
longs(code-box)
long
floats(code-box)
float
doubles(code-box)
double
chars(code-box)
char
booleans(code-box)
boolean
shorts(code-box)
short
bytes(code-box)
byte
classes(code-box)
class

@RepeatedTest annotation

To determine the functionality of your code, you may wish to test the same function numerous times with the same argument or input value.


JUnit 5 introduces the @RepeatedTest annotation in such cases. The primary goal of this @RepeatedTest is to allow us to test a piece of code numerous times with the same argument or input to see if it provides the desired output or not, even if it is performed multiple times.


To utilize the @RepeatedTest , just declare the value parameter and indicate the number of times the test case to be executed. That's all.


Isn't it straightforward?


 package in.learnjavaskills;

 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.RepetitionInfo;
 import org.junit.jupiter.api.TestInfo;

 public class RepeatTestExample
 {
    @RepeatedTest(value = 3, name = "{displayName} currentRepetition = {currentRepetition}, totalRepetitions = {totalRepetitions}" )
    @DisplayName("RepeatedTest")
    void repeatTestExample(RepetitionInfo repetitionInfo, TestInfo testInfo)
    {
        System.out.println(repetitionInfo.getCurrentRepetition());
    }
 }

You might want to look at the IDE console now to see how this @RepeatedTest works internally, don't you?


@RepeatedTest annotation IDE screen shot

Summary of the @Test, @ParameterizedTest and the @RepeatedTest

@Test @ParameterizedTest @RepeatedTest
Use Case When it is necessary to test a method only once, the @Test annotation is used. The value attribute of the @ParameterizedTest allows you to submit a variety of different inputs. @ParameterizedTest is handy if you want to test the same piece of code with different arguments at the same time. The @RepeatedTest annotation, as the name implies, can be used to repeat the same test scenario multiple times.
Attributes There are no properties associated with the @Test annotation. The @ParameterizedTest has no attributes but relies on @ValueSource, which has one attribute, to provide them. The @RepeatedTest annotation has two attributes: value, which specifies the number of times the test should be executed, and name, which specifies the name of the test.

@BeforeEach annotation

JUnit 5's @BeforeEach annotation is identical to JUnit 4's @Before annotation. The @BeforeEach method, as the name indicates, will run before each of the @Test , @ParameterizedTest , @RepeatedTest , or @TestFactory methods.


 package in.learnjavaskills;

 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;

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

 @DisplayName("Before each annotation example")
 class BeforeEachExample
 {
    @BeforeEach
    void beforeEach(TestInfo testInfo)
    {
        String displayName = testInfo.getDisplayName();
        System.out.println("Testing " + displayName);
    }

    @Test
    @DisplayName("test isEvenNumber method when passing odd number to the argument of isEvenNumber method")
    void testIsEvenNumberWithOddNumber()
    {
        int number = 5;
        Numbers numbers = new Numbers();
        boolean evenNumber = numbers.isEvenNumber(number);
        assertEquals(evenNumber, false);
    }

    @ParameterizedTest
    @ValueSource(ints = {2, 4, 6})
    @DisplayName("test isEvenNumber method when passing even number to the argument of isEvenNumber method")
    void testIsEvenNumberWhenEvenNumber(int number)
    {
        Numbers numbers = new Numbers();
        boolean evenNumber = numbers.isEvenNumber(number);
        assertEquals(evenNumber, true);
    }

    @RepeatedTest(value = 2)
    @DisplayName("isEvenNumber repeated test")
    void testIsEvenNumberRepeatedTest()
    {
        int number = 5;
        Numbers numbers = new Numbers();
        boolean evenNumber = numbers.isEvenNumber(number);
        assertEquals(evenNumber, false);
    }
 }

@BeforeEach annotation IDE output shreen shot

After running this class, the output console shows that the @BeforeEach annotated method was called before the other test method.


Testing [1] 2 Testing [2] 4 Testing [3] 6 Testing repetition 1 of 2 Testing repetition 2 of 2 Testing test isEvenNumber method when passing odd number to the argument of isEvenNumber method(code-box)

@AfterEach annotation

The @AfterEach annotation in JUnit 5 is equivalent to the @After annotation in JUnit 4. The @AfterEach method, as the name indicates, will be called after all test cases annotated with @Test , @ParameterizedTest , @RepeatedTest , or @TestFactory.


 package in.learnjavaskills;

 import org.junit.jupiter.api.*;

 import java.util.Objects;

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

 @DisplayName("After each annotation Example")
 class AfterEachExample
 {
    private Calculator calculator;

    @AfterEach
    void afterEach(TestInfo testInfo)
    {
        String methodName = testInfo.getTestMethod().get().getName();
        System.out.println("Successfully completed executing " + methodName + " test case");
        if (Objects.nonNull(calculator))
            calculator = null;
        // One of the real time example of using @AfterEach, you can close the DB connection.
    }

    @BeforeEach
    void beforeEach(TestInfo testInfo)
    {
        // One of the real time example of using the @BeforeEach,
        // you can initialize the fresh variable each time before executing the test cases.
        String methodName = testInfo.getTestMethod().get().getName();
        System.out.println("Initializing calculator for " + methodName + " method");
        calculator = new Calculator();
    }


    @Test
    void tesAdditionFunction()
    {
        int sum = calculator.add(5, 5);
        assertEquals(sum, 10);
    }

    @Test
    void tesMultiplicationFunction()
    {
        int multiplied = calculator.multiply(5, 5);
        assertEquals(multiplied, 25);
    }
 }

If you carefully observe the console after running the earlier program, you will notice that the @AfterEach annotation method was executed one by one once the test case was done.


Initializing calculator for tesAdditionFunction method Successfully completed executing tesAdditionFunction test case Initializing calculator for tesMultiplicationFunction method Successfully completed executing tesMultiplicationFunction test case(code-box)

@BeforeAll and @AfterAll annotations

JUnit 5's @BeforeAll and @AfterAll annotations are similar to JUnit 4's @BeforeClass and @AfterClass annotations. These two annotations are used before or after running @Test , @RepeatedTest , @ParameterizedTest , and @TestFactory.


If you use these two annotations, @BeforeAll and @AfterAll, be sure that both of their methods are static, else you will get the following exception:


 org.junit.platform.commons.JUnitException: @BeforeAll method 'void in.learnjavaskills.BeforeAllAndAfterAllExample.beforeAll(org.junit.jupiter.api.TestInfo)' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:85)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findMethodsAndAssertStaticAndNonPrivate$0(LifecycleMethodUtils.java:62)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1092)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

 package in.learnjavaskills;

 import org.junit.jupiter.api.*;

 import java.util.Objects;

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

 @DisplayName("BeforeAll and AfterAll annotation example")
 class BeforeAllAndAfterAllExample
 {
    private static Calculator calculator;

    @BeforeAll
    static void beforeAll(TestInfo testInfo)
    {
        // Just like @BeforeEach, You can use @BeforeAll to open you database connection or initialize new variable.
        System.out.println("before all");
        calculator = new Calculator();
    }

    @AfterAll
    static void afterAll()
    {
        // It's best practise to close the DB connection or closing the stream in the @AfterAll.
        // Just for the example I'm using the  Calculator service.
        System.out.println("after all");
        if (Objects.nonNull(calculator))
            calculator = null;
    }

    @Test
    void test()
    {
        System.out.println("Started test case");
        int sum = calculator.add(1, 1);
        assertEquals(sum, 2);
        System.out.println("Completed test case");
    }
 }

After running the earlier programs, you may check the console to see the sequence of execution.


before all Started test case Completed test case after all(code-box)

Summary of the @BeforeEach, @AfterEach, @BeforeAll and @AfterAll

The @BeforeEach, @AfterEach, @BeforeAll, and @AfterAll are summarized below.


@BeforeEach @AfterEach @BeforeAll @AfterAll
Definations The @BeforeEach annotated method, as the name indicates, will run before each of the test method. The @AfterEach annotated method, as the name indicates, will be called after all test cases. The @BeforeAllannotated method will run before the test case and just once per class. After all test cases have been finished, the @AfterAll annotated method will be called.
Execution sequence 2 3 1 4
Is it necessary to have a static method? NO NO YES YES

@Disabled annotation

There may be times when you need to disable the test case. In such circumstances, we may use JUnit 5's @Disabled annotation, which is comparable to JUnit 4's @Ignore annotation.


The @Disabled annotation can be applied to methods or classes.


 package in.learnjavaskills;

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

 public class DisableExample
 {
    @Disabled
    @Test
    void test()
    {

    }
 }

@Timeout annoation

You could wish to test your code, which should run in a reasonable amount of time. In this instance, you may use @Timeout in your test case to check the execution time.


If the test case is not completed on time, the @Timeout annotation throws a TimeoutException. When the execution of the test case does not complete inside the time range specified, look at the TimeoutException following in detail.


 java.util.concurrent.TimeoutException: shouldFailAfterOneSecond() timed out after 1 second
	at org.junit.jupiter.engine.extension.TimeoutExceptionFactory.create(TimeoutExceptionFactory.java:29)
	at org.junit.jupiter.engine.extension.SameThreadTimeoutInvocation.proceed(SameThreadTimeoutInvocation.java:58)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
		at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
	Suppressed: java.lang.InterruptedException: sleep interrupted
		at java.base/java.lang.Thread.sleep(Native Method)
		at in.learnjavaskills.TimeOutExample.shouldFailAfterOneSecond(TimeOutExample.java:12)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

 package in.learnjavaskills;

 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;

 import java.util.concurrent.TimeUnit;

 @Timeout(5)
 class TimeOutExample
 {
    @Test
    @Timeout(1)
    void shouldFailAfterOneSecond() throws InterruptedException
    {
        Thread.sleep(1001);
    }
 }

Default timeout for duration for each test case

We've already seen how to use the @Timeout annotation at the method level, however we can also use the @Timeout annotation at the class level to set up the default timeout period for each test case.


 package in.learnjavaskills;

 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;

 import java.util.concurrent.TimeUnit;

 @Timeout(5)
 class TimeOutExample
 {
    @Test
    @Timeout(1)
    void shouldFailAfterOneSecond() throws InterruptedException
    {
        Thread.sleep(1001);
    }
	
	@Test
    void shouldFailAfterFiveSecondWhichIsDefaultTimeout() throws InterruptedException
    {
        Thread.sleep(5001);
    }
 }

The value and the unit attribute of the @Timeout annotation

So far, we have been able to time out test cases, but we have only seen how to pass a single attribute, i.e., time unit, in a second's time in @Timeout. But what if we want the time unit to be specified in minutes, hours, or days?


We may utilize the @Timeout annotation's value and unit attribute to specify the time duration.


 package in.learnjavaskills;

 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;

 import java.util.concurrent.TimeUnit;

 class TimeOutExample
 {
   
    @Test
    @Timeout(value = 1, unit = TimeUnit.MINUTES)
    void shouldTimeoutAfterOneMinutes() throws InterruptedException
    {
        Thread.sleep(1_00_000);
    }
 }

Conclussion

So far, we have learned about the JUnit 5. We discuss the importance of JUnit 5, and we see the architecture of JUnit 5.


We learn about several JUnit 5 annotations that are often used in the industry. We not only analyze the annotations, but we also do hands-on lab activities using a real-world scenario.


We additionally looked at several JUnit 5 annotations to better understand the use case and its significance.


Keep learning and keep growing.

(getButton) #text=(Next: What is Mockito?) #icon=(link) #color=(#2339bd)

Post a Comment

0 Comments
Post a Comment (0)

#buttons=(Accept !) #days=(20)

Our website uses cookies to enhance your experience. Learn More
Accept !
To Top