JUnit 5 features that every Java Developer should know by now.
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 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 theTestEngine
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 aTestEngine
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 aMethodOrderer
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 asassertEquals
,assertTrue
,assertFalse
, and many others. You can also use theassertThrows
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 theDynamicTest.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 asassertEquals
,assertTrue
, andassertFalse
. In JUnit 5, assertions are made using static methods on theorg.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 customTestExecutionOrderProvider
. - 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 theJUnitPlatform
class, or by using theMaven Surefire
orGradle
plugins. - Exception testing: In JUnit 4, exception testing is done using the
@Test
annotation and theexpected
attribute. In JUnit 5, exception testing is done using theassertThrows
method on theAssertions
class. - Timeouts: In JUnit 4, timeouts are specified using the
@Test
annotation and thetimeout
attribute. In JUnit 5, timeouts are specified using theassertTimeout
method on theAssertions
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 theLifecycle.PER_CLASS
value, or by using the@DependentOn
annotation.