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.
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.
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 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.
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.
@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.
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 |
String |
ints |
int |
longs |
long |
floats |
float |
doubles |
double |
chars |
char |
booleans |
boolean |
shorts |
short |
bytes |
byte |
classes |
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?
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);
}
}
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 @BeforeAll annotated 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)