JUnit 5 features that every Java Developer should know by now.

FullStackTips
6 min readDec 28, 2022

--

This story covers some fundamental details of Junit5 and the differences between JUnit4 and JUnit5 at the end.

JUnit 5 is a popular testing framework for Java that allows developers to write and run tests at the unit, integration, and system levels. It is an open-source project that is widely used in the Java ecosystem, and it is designed to be extensible, flexible, and easy to use.

Junit 5 architecture taken from (https://freecontent.manning.com/)

JUnit 5 is composed of several different modules that provide various features and capabilities. Here are the main modules that make up JUnit 5:

  • JUnit Platform: This module defines the infrastructure for running tests on the JUnit Platform, including the JUnitPlatform class that is used to discover and execute tests. It also provides the TestEngine API, which allows third-party test frameworks to integrate with the JUnit Platform.
  • JUnit Jupiter: This module provides the APIs for writing and running tests in JUnit 5. It includes annotations such as @Test, @BeforeAll, and @AfterAll, as well as a range of assert methods and other utility classes.
  • JUnit Vintage: This module provides support for running JUnit 3 and JUnit 4 tests on the JUnit Platform. It includes a JUnit4 runner that can be used to execute JUnit 4 tests, as well as a TestEngine implementation that allows JUnit 4 tests to be run on the JUnit Platform.
  • JUnit Jupiter Engine: This module provides the implementation of the TestEngine API for the JUnit Jupiter test framework. It is responsible for discovering and executing tests written using the JUnit Jupiter APIs.

These are the main modules that make up JUnit 5, but there are also a number of other modules that provide additional functionality, such as support for different testing idioms (e.g., dynamic tests, nested tests), support for different testing environments (e.g., the junit-jupiter-engine-params module for parameterized tests), and support for different test execution environments (e.g., the junit-jupiter-engine-selenium module for running tests in a Selenium web browser).

Here’s an example of a simple test class that verifies that a method for calculating the area of a rectangle is working correctly:

To run this test, you would use a JUnit runner, such as the JUnitPlatform class. This class is responsible for discovering and executing the tests. You can run the tests from the command line or from within an Integrated Development Environment (IDE) by using a test runner plugin.

For example, to run the tests from the command line using Maven, you would use the following command:

mvn clean test

JUnit 5 also provides a number of additional features and annotations that can be used to customize the behavior of the tests. For example, you can use the @BeforeAll and @AfterAll annotations to specify methods that should be run before and after all of the tests in the test class, respectively. You can also use the @Disabled annotation to temporarily disable a test, or the @RepeatedTest annotation to run a test multiple times with different parameters.

  • Test execution order: By default, JUnit 5 executes tests in an undefined order. This means that the order in which tests are run can change from one run to the next. If you want to specify a particular execution order for your tests, you can use the @TestMethodOrder annotation to specify a MethodOrderer that determines the order in which the tests are run.
  • Assertions: JUnit 5 provides a range of assert methods that you can use to verify the behavior of your code. These methods are defined in the Assertions class and include methods such as assertEquals, assertTrue, assertFalse, and many others. You can also use the assertThrows method to verify that a specific exception is thrown when a certain method is called.
  • Parameterized tests: JUnit 5 allows you to write tests that are parameterized, which means that they can be run multiple times with different sets of input values. This is useful when you want to test a method with a large number of different input combinations. To write a parameterized test, you can use the @ParameterizedTest annotation and specify the input values using the @ValueSource, @CsvSource, or @MethodSource annotations.
  • Dynamic tests: JUnit 5 also supports the creation of dynamic tests, which are tests that are generated at runtime based on some logic. This can be useful when you want to test a large number of similar cases, or when you want to generate test cases based on external data. To write a dynamic test, you can use the @TestFactory annotation and use the DynamicTest.stream method to generate a stream of dynamic tests.

Here’s an example of a test class that demonstrates a number of the features and capabilities of JUnit 5:

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Random;

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

@TestMethodOrder(Alphanumeric.class)
public class MyTestClass {

private static Random random;

@BeforeAll
public static void setup() {
random = new Random();
}

@BeforeEach
public void beforeEach(TestInfo testInfo, TestReporter testReporter) {
testReporter.publishEntry("Running test: " + testInfo.getDisplayName());
}

@Test
@DisplayName("Test that verifies the behavior of the add() method")
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(3, calculator.add(1, 2));
}

@Disabled
@Test
public void testDisabled() {
// This test will not be run
}

@RepeatedTest(5)
public void testRandomNumber(TestReporter testReporter) {
int number = random.nextInt();
testReporter.publishEntry("Generated random number: " + number);
assertTrue(number >= 0);
}

@ParameterizedTest
@CsvSource({"1, 2, 3", "4, 5, 9", "6, 7, 13"})
public void testAddWithParameters(int x, int y, int expectedResult) {
Calculator calculator = new Calculator();
assertEquals(expectedResult, calculator.add(x, y));
}

@ParameterizedTest
@ValueSource(ints = {1, 3, 5, 7, 9})
public void testIsOdd(int x) {
Calculator calculator = new Calculator();
assertTrue(calculator.isOdd(x));
}

@Test
public void testDivideByZero() {
Calculator calculator = new Calculator();
assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
}
}

Differences between JUnit4 and JUnit5:

One key difference between JUnit 4 and JUnit 5 is that JUnit 5 is designed to work with newer versions of Java, including Java 8 and above. JUnit 4, on the other hand, is compatible with older versions of Java. JUnit 5 also introduces a number of new features and improvements that are not present in JUnit 4, as described above.

In terms of usage, there are also some differences between the two versions. For example, JUnit 5 uses the @Test annotation to mark a method as a test method, whereas JUnit 4 uses the @Test annotation as well as the @RunWith annotation to specify the test runner. There are also some differences in how test execution and results are reported in the two versions.

Here are some additional differences between JUnit 4 and JUnit 5:

  • Assertions: In JUnit 4, assertions are made using static methods on the org.junit.Assert class, such as assertEquals, assertTrue, and assertFalse. In JUnit 5, assertions are made using static methods on the org.junit.jupiter.api.Assertions class, which includes the same methods as well as additional methods for asserting about exceptions and asynchronous execution.
  • Test execution order: In JUnit 4, test methods are executed in the order they are declared in the source code. In JUnit 5, test execution order is not specified by default, and can be customized using the @Order annotation or by using a custom TestExecutionOrderProvider.
  • Test suites: In JUnit 4, test suites are created using the @RunWith and @Suite annotations. In JUnit 5, test suites are created using the @TestSuite annotation, or by using the @SelectPackages, @SelectClasses, or @SelectPackages annotations to specify the tests to include in the suite.
  • Test execution: In JUnit 4, tests are executed using a JUnitCore instance, which can be run from the command line or within an IDE. In JUnit 5, tests can be executed using the JUnitPlatform class, or by using the Maven Surefire or Gradle plugins.
  • Exception testing: In JUnit 4, exception testing is done using the @Test annotation and the expected attribute. In JUnit 5, exception testing is done using the assertThrows method on the Assertions class.
  • Timeouts: In JUnit 4, timeouts are specified using the @Test annotation and the timeout attribute. In JUnit 5, timeouts are specified using the assertTimeout method on the Assertions class.
  • Repeated tests: In JUnit 4, repeated tests are implemented using the @RepeatedTest annotation and the @BeforeEach and @AfterEach annotations. In JUnit 5, repeated tests are implemented using the @RepeatedTest annotation and the @BeforeEach and @AfterEach annotations, or by using the @RepeatedTest annotation and the @BeforeEach and @AfterEach annotations in combination with the @TestTemplate annotation.
  • Test dependencies: In JUnit 4, test dependencies are specified using the @DependsOn annotation. In JUnit 5, test dependencies are specified using the @TestInstance annotation and the Lifecycle.PER_CLASS value, or by using the @DependentOn annotation.

--

--

FullStackTips

I am full stack developer with over 15 years of experience in various programming languages. https://medium.com/@fullstacktips/membership