New Architecture and Advanced features – JUnit5

“Quality is never an accident; it is always the result of intelligent effort.” – John Ruskin


To make a product better, reduce error, improve code quality and to create ultimate documentation testing is important.

David Ogilvy Quote

Unit testing is the first and most important step which is the continuous process followed along software development process. This blog is about JUnit5 testing framework as it is most useful and easy tool used for unit testing.

What is JUnit?

Junit is a unit testing framework for the Java programming language. JUnit has been important in the development of test-driven development, and is one of a family of unit testing frameworks. Its main use is to write repeatable tests for your application code units.

What is JUnit5?

Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage


JUnit 5 Architecture.png

JUnit Platform

The platform is responsible for launching testing frameworks on the JVM. It defines a stable and powerful interface between JUnit and its client such as build tools. It also provides a Console Launcher to launch the platform from the command line and build plugins for Gradle and Maven.

JUnit Jupiter

This module includes new programming and extension models for writing tests in JUnit 5. New annotations included are:

@TestFactory Denotes that a method is a test factory for dynamic tests.

@DisplayName Declares a custom display name for the test class or test method.

@Nested Denotes that the annotated class is a non-static nested test class. @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class unless the “per-class” test instance lifecycle is used.

@Tag Used to declare tags for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4.

@ExtendWith Used to register extensions declaratively. Such annotations are inherited.

@BeforeEach Denotes that the annotated method should be executed before each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @Before.

@AfterEach Denotes that the annotated method should be executed after each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @After.

@BeforeAll Denotes that the annotated method should be executed before all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @BeforeClass.

@AfterAll Denotes that the annotated method should be executed after all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @AfterClass.

@Disabled Used to disable a test class or test method; analogous to JUnit 4’s @Ignore. Such annotations are not inherited.

JUnit Vintage Supports running JUnit 3 and JUnit 4 based tests on the JUnit 5 platform.

JUnit5 Test Execution


JUnit Life Cycle via

Practice

public class JunitTest {
    @BeforeAll
    static void setup(){
        System.out.println("@BeforeAll--");
    }
    @BeforeEach
    void setupEach(){
        System.out.println("@BeforeEach--");
    }
    @Test
    void testCalcOne()
    {
        System.out.println("======TEST ONE EXECUTED=======");
        Assertions.assertEquals( 6 , Calculator.add(3, 3));
    }
    @Test
    void testCalcTwo()
    {
        System.out.println("======TEST TWO EXECUTED=======");
        Assertions.assertEquals( 9 , Calculator.add(3, 6));
    }
    @AfterEach
    void endupEach(){
        System.out.println("@AfterEach--");
    }
    @AfterAll
    static void endup(){
        System.out.println("@AfterAll--");
    }
}

Output

@BeforeAll--
@BeforeEach--
======TEST ONE EXECUTED=======
@AfterEach--
@BeforeEach--
======TEST TWO EXECUTED=======
@AfterEach--
@AfterAll--

Coolest New Features:

  1. Nested unit tests

The new @Nested annotation feature of JUnit 5 lets you create cleaner unit test classes by using arbitrarily nested inner classes as additional units of test composition within a single test class. This allows you to keep the tests together, while at the same time allowing you to initialize them differently to simulate different runtime conditions.

Practice

@DisplayName("JUnit 5 Nested Example")
class JUnit5NestedTestExample {
    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all test methods");
    }
    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test method");
    }
    @AfterEach
    void afterEach() {
        System.out.println("After each test method");
    }
    @AfterAll
    static void afterAll() {
        System.out.println("After all test methods");
    }
@Nested
    @DisplayName("Tests for the method Nested1")
    class Nested1 {
        @BeforeEach
        void beforeEach() {
            System.out.println("Before each test method of the Nested1 class");
        }
        @AfterEach
        void afterEach() {
            System.out.println("After each test method of the Nested1 class");
        }
        @Test
        @DisplayName("Example test for method Nested1")
        void sampleTestForMethodA() {
            System.out.println("Example test for method Nested1");
        }
    }
@Nested
        @DisplayName("When Y is true")
        class WhenX {
            @BeforeEach
            void beforeEach() {
                System.out.println("Before each test method of the WhenY class");
            }
            @AfterEach
            void afterEach() {
                System.out.println("After each test method of the WhenY class");
            }
            @Test
            @DisplayName("Example test for method Nested1 when Y is true")
            void sampleTestForMethodNested1WhenY() {
                System.out.println("Example test for method Nested1 when Y is true");
            }
        }
    }
}

Output

Before all test methods
Before each test method
Before each test method of the Nested1 class
Example test for method Nested1
After each test method of the Nested1 class
After each test method
Before each test method
Before each test method of the Nested1 class
Before each test method of the WhenY class
Example test for method Nested1 when Y is true
After each test method of the WhenY class
After each test method of the Nested1 class
After each test method
After all test methods
  1. Junit ExtensionsThe JUnit 5 Extension Model follows the JUnit 5 team’s core principle of “Prefer extensions over features” and defines a number of extension points for which default implementations have been provided, but for which you may create Extensions to override the default behavior. These Extensions are callback interfaces that you implement and then register with the JUnit 5 framework, and when that extension point is reached when running a unit test, your code is invoked.
  2. Conditionsyou can create your own custom extension of one of both of the conditional test execution API methods to inspect the currently running test and decide on-the-fly, whether or not to run a test class (via ContainerExecutionCondition) or method (via TestExecutionCondition).
  3. Lambda SupportAnd now that feature is baked into JUnit 5 through the use of a few new FunctionalInterfaces like Executable. Methods like Assertions.assertAll() take Executable… as a varargs parameter for which you can substitute a lambda expression.
@Test
void groupedAssertions() {
    // In a grouped assertion all assertions are executed, and all
    // failures will be reported together.
    assertAll("person",
        () -> assertEquals("Ram", person.getFirstName()),
        () -> assertEquals("Sham", person.getLastName())
    );
}
  1. Parameterized Tests

With parameterized tests, JUnit 5 allows you to specify one or more sources that supply your unit test method with the parameter values it needs to run its tests. The source generates the argument values that JUnit 5 then passes to your unit test method, one-at-a-time, until all argument values in the source are exhausted.

@ParameterizedTest
@ValueSource(strings = { "Hi", "Hello" })
void testWithStringParameter(String argument) {
    assertNotNull(argument);
}

Difference between previous version JUint4 and JUnit5

Supported Java Versions

JUnit4 – Java 5 or higher JUnit5 – Java 8 or higher

Architecture

JUnit4 – All in one JUnit5 – There are 3 separated modules:

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

Annotations
Annotations
Assertions
Assertions

Assert methods in JUnit 5 can be used with Java 8 Lambdas.

assertTrue(4 == 4, () -> "Assertion messages can be provided by Java 8 Lambdas ");
Throwable exception = expectThrows(IllegalArgumentException.class, () -> {
    throw new IllegalArgumentException("Invalid page.");
});

Assumptions

Assertions

JUnit 5 provides fewer assumptions than JUnit 4. Note that JUnit 5 overloads each its assumption method to be used with Java 8 Lambda Expressions, for eg:

assumeTrue("QA".equals(System.getenv("ENV")),
            () -> "Should run on QA environment");

Repeated Tests

A new feature in JUnit5 which allows us to repeat a test in a specified number of times is Repeated Tests. Let’s see an example which declares a test that will be repeated in 50 times:

@RepeatedTest(50)
void repeatedTest() {
    System.out.println("Hello World!");
}

Dynamic Tests

JUnit 5 introduces the concept of Dynamic Tests which are tests that can be generated at runtime by a factory method. Let’s see an example which we generate 2 tests at runtime:

@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
    return Arrays.asList(
        dynamicTest("First dynamic test", () -> assertTrue(true)),
        dynamicTest("Second dynamic test", () -> assertEquals(6, 2 * 3))
    );
}