JUnit 5

Javaunit testingtesting frameworkTDDJupiterplatformassertionslifecycle

JUnit 5

Overview

JUnit 5 is the most popular unit testing framework for Java. JUnit 5 (Jupiter) leverages modern Java features and provides an extensible architecture. It supports test-driven development (TDD) and includes advanced features like parameterized tests, dynamic tests, and conditional test execution.

Details

Technical Features

  • JUnit Platform: Foundation for launching testing frameworks
  • JUnit Jupiter: New programming and extension model
  • JUnit Vintage: Backward compatibility for JUnit 3/4 based tests
  • Java 8+ Support: Utilizes lambda expressions and Stream API
  • Flexible Assertions: Rich assertion methods with detailed error messages

Architectural Evolution

JUnit 5 has evolved significantly from JUnit 4, consisting of three sub-projects. The Jupiter API introduces a new annotation system and allows flexible framework extension through the extension model. It natively supports modern testing approaches like parameterized tests and dynamic tests.

Ecosystem

Deep integration with major Java tools like Spring Boot, Gradle, and Maven, with excellent IDE support. Rich extension libraries and third-party plugins enable various testing scenarios.

Advantages and Disadvantages

Advantages

  1. Modern Java Features: Maximizes use of Java 8+ features
  2. Rich Assertions: Expressive and readable test code
  3. Flexible Extensibility: Easy creation of custom extensions
  4. Parameterized Tests: Efficient testing with multiple test data
  5. Conditional Tests: Environment-based test execution control
  6. Excellent IDE Integration: Native support in major IDEs
  7. Detailed Error Reporting: Comprehensive failure information for debugging

Disadvantages

  1. Increased Learning Cost: Learning burden when migrating from JUnit 4
  2. Configuration Complexity: Setup can become complex when using advanced features
  3. Library Size: Larger than JUnit 4
  4. Migration Cost: Work required to migrate existing JUnit 4 test code

Reference Pages

Usage Examples

Hello World (Basic Test)

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

class MyFirstJUnitJupiterTests {

    @Test
    void myFirstTest() {
        // Basic assertions
        assertTrue(true);
        assertEquals(4, 2 + 2);
        assertNotNull("Hello World");
    }

    @Test
    void testWithCustomMessage() {
        int expected = 10;
        int actual = 5 + 5;
        assertEquals(expected, actual, "Calculation result does not match expected value");
    }
}

Assertion Examples

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;

class AssertionExamples {

    @Test
    void standardAssertions() {
        assertEquals(2, 1 + 1);
        assertTrue(1 > 0, "1 is greater than 0");
        assertFalse(1 < 0, "1 is not less than 0");
        assertNull(null);
        assertNotNull("not null");
    }

    @Test
    void groupedAssertions() {
        // Grouped assertions
        assertAll("person",
            () -> assertEquals("John", "John"),
            () -> assertEquals("Doe", "Doe")
        );
    }

    @Test
    void dependentAssertions() {
        // Dependent assertions
        assertAll(
            () -> {
                String firstName = "John";
                assertNotNull(firstName);
                
                // Execute only if previous assertion succeeded
                assertAll("firstName",
                    () -> assertTrue(firstName.startsWith("J")),
                    () -> assertTrue(firstName.endsWith("n"))
                );
            }
        );
    }

    @Test
    void exceptionTesting() {
        // Exception testing
        Exception exception = assertThrows(
            IllegalArgumentException.class, 
            () -> Integer.parseInt("abc")
        );
        assertEquals("For input string: \"abc\"", exception.getMessage());
    }

    @Test
    void timeoutTesting() {
        // Timeout testing
        assertTimeout(Duration.ofMillis(100), () -> {
            Thread.sleep(50);
        });
    }
}

Lifecycle Methods

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

class LifecycleMethodsTest {

    @BeforeAll
    static void initAll() {
        System.out.println("Executed once before test class starts");
    }

    @BeforeEach
    void init() {
        System.out.println("Executed before each test method");
    }

    @Test
    void succeedingTest() {
        assertTrue(true, "Succeeding test");
    }

    @Test
    void failingTest() {
        // Example of intentionally failing test
        // fail("Intentionally failed test");
        assertTrue(true, "Actually succeeds");
    }

    @Test
    @Disabled("This test is disabled")
    void skippedTest() {
        // This test will be skipped
    }

    @AfterEach
    void tearDown() {
        System.out.println("Executed after each test method");
    }

    @AfterAll
    static void tearDownAll() {
        System.out.println("Executed once after test class ends");
    }
}

Parameterized Tests

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;
import java.time.Month;
import java.util.stream.Stream;

class ParameterizedTestExamples {

    @ParameterizedTest
    @ValueSource(strings = {"racecar", "radar", "able was I ere I saw elba"})
    void palindromes(String candidate) {
        assertTrue(StringUtils.isPalindrome(candidate));
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 5, 8, 13, 21})
    void testWithIntValues(int argument) {
        assertTrue(argument > 0 && argument < 25);
    }

    @ParameterizedTest
    @CsvSource({
        "apple,         1",
        "banana,        2",
        "'lemon, lime', 0xF1",
        "strawberry,    700_000"
    })
    void testWithCsvSource(String fruit, int rank) {
        assertNotNull(fruit);
        assertNotEquals(0, rank);
    }

    @ParameterizedTest
    @EnumSource(Month.class)
    void testWithEnumSource(Month month) {
        int quarter = (month.getValue() - 1) / 3 + 1;
        assertTrue(quarter >= 1 && quarter <= 4);
    }

    @ParameterizedTest
    @MethodSource("stringProvider")
    void testWithExplicitLocalMethodSource(String argument) {
        assertNotNull(argument);
    }

    static Stream<String> stringProvider() {
        return Stream.of("apple", "banana");
    }
}

Conditional Tests

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

class ConditionalTestExamples {

    @Test
    @EnabledOnOs(OS.MAC)
    void onlyOnMacOs() {
        // Run only on macOS
        assertTrue(System.getProperty("os.name").contains("Mac"));
    }

    @Test
    @EnabledOnOs({OS.LINUX, OS.MAC})
    void onLinuxOrMac() {
        // Run only on Linux or macOS
        assertTrue(true);
    }

    @Test
    @DisabledOnOs(OS.WINDOWS)
    void notOnWindows() {
        // Run on non-Windows
        assertTrue(true);
    }

    @Test
    @EnabledOnJre(JRE.JAVA_11)
    void onlyOnJava11() {
        // Run only on Java 11
        assertTrue(true);
    }

    @Test
    @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
    void onlyOn64BitArchitectures() {
        // Run only on 64-bit architectures
        assertTrue(true);
    }

    @Test
    @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
    void onlyOnStagingServer() {
        // Run only when specific environment variable is set
        assertTrue(true);
    }
}

Tagging and Filtering

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

@Tag("fast")
class TaggedTests {

    @Test
    @Tag("taxes")
    void testingTaxCalculation() {
        assertTrue(true);
    }

    @Test
    @Tag("slow")
    @Tag("integration")
    void testingDatabaseQuery() {
        // Database integration test
        assertTrue(true);
    }

    @Test
    @Tag("unit")
    void fastUnitTest() {
        // Fast unit test
        assertEquals(4, 2 + 2);
    }
}

// Tag filtering configuration example
// junit-platform.properties:
// junit.jupiter.execution.parallel.enabled=true
// junit.jupiter.execution.parallel.mode.default=concurrent

// Gradle tag filtering example:
// test {
//     useJUnitPlatform {
//         includeTags("fast", "unit")
//         excludeTags("slow", "integration")
//     }
// }