Open In App

JUnit 5 @Nested Test Classes

Last Updated : 18 Oct, 2024
Summarize
Comments
Improve
Suggest changes
Share
Like Article
Like
Report

JUnit 5 introduced several powerful features to make testing in Java more flexible and expressive. One of these features is the @Nested test classes, which allows for better organization of test cases by logically grouping them inside an outer test class. This can be particularly useful when dealing with complex scenarios or hierarchical structures that require the tests to be categorized and scoped in a clear and readable manner.

In this article, we will explore the @Nested annotation in JUnit 5, discussing its signature, how it improves test organization, and how we can implement it effectively in our test suite. By the end, you will have a deep understanding of nested test classes and their applications.

Prerequisites:

  • Basic understanding of Java programming.
  • Familiarity with JUnit 5.
  • Knowledge of Maven as a build dependency management system.
  • JDK and IntelliJ IDEA installed in your system.

What is JUnit 5 @Nested Test Class?

The @Nested annotation in JUnit 5 is used to declare inner test classes that are logically grouped within the outer test class. It provides an easy and readable way to organize related test cases under different categories. The @Nested test class can access the outer class's instance variables and methods, sharing the same state and lifecycle as the outer test class.

Key Characteristics of @Nested Test Classes:

  • Non-static inner classes: The test class annotated with @Nested must be non-static, meaning it has access to the outer class's fields and methods.
  • Logical grouping: It allows you to group related test cases based on different functionalities, use cases, or workflows.
  • Test scope sharing: The @Nested class can share the outer class context and setup, making it easier to write tests with a shared state or resources.
  • Better readability: Test cases are naturally organized, resulting in a cleaner and more readable test suite.

Benefits of Using @Nested Test Classes

Here are some major benefits of using @Nested test classes in unit tests:

  • Improved Test Organization: Nested test classes allow you to categorize related test methods into separate groups, especially useful for larger classes with distinct functionalities.
  • Shared Test Context: The nested test class has access to the outer test class’s variables and methods, eliminating the need to duplicate setup and teardown logic in every nested class.
  • Reduced Boilerplate Code: By using the shared context, you can avoid redundant code.
  • Test Clarity: Nested classes provide a clear structure, enhancing the readability and maintainability of the test code.

Implementation of @Nested Test Classes of JUnit

Let's create an example project using JUnit 5 @Nested test classes, demonstrating how to organize tests using nested classes to handle different test scenarios.

Step 1: Create a New Maven Project

Create a new Maven project using IntelliJ IDEA. Choose the following options:

  • Name: nested-tests-example
  • Build System: Maven

Click on the Create button.

Project Metadata

Project Structure

After project creation done successfully, the folder structure will look like the below image:

Project Folder Structure

Step 2: Add JUnit 5 Dependencies to pom.xml

Open the pom.xml file and add the following dependencies for JUnit 5.

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gfg</groupId>
    <artifactId>nested-tests-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- JUnit 5 API -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.9.0</version>
            <scope>test</scope>
        </dependency>

        <!-- JUnit 5 Test Engine -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.9.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
        </plugins>
    </build>

</project>

Step 3: Create the Calculator Class

Create the Calculator class, which provides basic mathematical operations.

Calculator.java:

Java
package com.gfg;

public class Calculator {

    // Adds two numbers
    public int add(int a, int b) 
    {
        return a + b;
    }

    // Subtracts one number from another
    public int subtract(int a, int b) 
    {
        return a - b;
    }

    // Multiplies two numbers
    public int multiply(int a, int b) 
    {
        return a * b;
    }

    // Divides one number by another; throws an exception when dividing by zero
    public int divide(int a, int b) 
    {
        if (b == 0) 
        {
            throw new ArithmeticException("Cannot divide by zero");
        }
        return a / b;
    }
}

Step 4: Main Class

This class demonstrates the usage of the Calculator class.

Java
package com.gfg;

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        // Example usage of Calculator methods
        int additionResult = calculator.add(10, 5);
        System.out.println("Addition (10 + 5): " + additionResult);

        int subtractionResult = calculator.subtract(10, 5);
        System.out.println("Subtraction (10 - 5): " + subtractionResult);

        int multiplicationResult = calculator.multiply(10, 5);
        System.out.println("Multiplication (10 * 5): " + multiplicationResult);

        try {
            int divisionResult = calculator.divide(10, 2);
            System.out.println("Division (10 / 2): " + divisionResult);
        } catch (ArithmeticException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Explanation:

  • The Main class demonstrates how to use the Calculator class methods for addition, subtraction, multiplication, and division.
  • It handles exceptions for division by zero.

Step 5: Create the CalculatorTest Class (Unit Tests with @Nested)

Create the CalculatorTest class and write the test cases using JUnit 5's @Nested feature.

CalculatorTest.java:

Java
import com.gfg.Calculator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    private Calculator calculator; // Instance of Calculator for testing

    @BeforeEach
    void setup() {
        calculator = new Calculator(); // Initialize Calculator before each test
    }

    // Nested class for testing addition
    @Nested
    class AdditionTests {
        @Test
        void shouldReturnCorrectSumForTwoPositiveNumbers() {
            assertEquals(5, calculator.add(2, 3)); // Test addition
        }

        @Test
        void shouldReturnCorrectSumForPositiveAndNegativeNumber() {
            assertEquals(2, calculator.add(5, -3)); // Test addition with negative number
        }

        @Test
        void shouldReturnZeroWhenAddingZero() {
            assertEquals(5, calculator.add(5, 0)); // Test addition with zero
        }
    }

    // Nested class for testing subtraction
    @Nested
    class SubtractionTests {
        @Test
        void shouldReturnCorrectDifferenceForTwoPositiveNumbers() {
            assertEquals(2, calculator.subtract(5, 3)); // Test subtraction
        }

        @Test
        void shouldReturnCorrectDifferenceForPositiveAndNegativeNumber() {
            assertEquals(8, calculator.subtract(5, -3)); // Test subtraction with negative number
        }

        @Test
        void shouldReturnZeroWhenSubtractingSameNumbers() {
            assertEquals(0, calculator.subtract(5, 5)); // Test subtraction of same numbers
        }
    }

    // Nested class for testing multiplication
    @Nested
    class MultiplicationTests {
        @Test
        void shouldReturnCorrectProductForTwoPositiveNumbers() {
            assertEquals(15, calculator.multiply(3, 5)); // Test multiplication
        }

        @Test
        void shouldReturnZeroWhenMultiplyingWithZero() {
            assertEquals(0, calculator.multiply(5, 0)); // Test multiplication with zero
        }

        @Test
        void shouldReturnNegativeProductWhenOneNegativeNumber() {
            assertEquals(-15, calculator.multiply(-3, 5)); // Test multiplication with negative number
        }
    }

    // Nested class for testing division
    @Nested
    class DivisionTests {
        @Test
        void shouldReturnCorrectQuotientForTwoPositiveNumbers() {
            assertEquals(2, calculator.divide(10, 5)); // Test division
        }

        @Test
        void shouldThrowExceptionWhenDividingByZero() {
            assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0)); // Test division by zero
        }

        @Test
        void shouldReturnNegativeQuotientWhenDividingWithNegativeNumber() {
            assertEquals(-2, calculator.divide(-10, 5)); // Test division with negative number
        }
    }
}
  • Class Setup: The CalculatorTest class uses @BeforeEach to initialize a new Calculator instance before each test.
  • Nested Classes:
    • Each nested class (e.g., AdditionTests, SubtractionTests) groups related test cases, improving organization and readability.
    • Each test method is annotated with @Test, indicating that it is a test case.

Step 6: Run the Application

After the project completed, run the application, and it will show the below output:

Console Output

Step 7: Running the Tests

To run the tests, execute the following Maven command in your terminal:

mvn test

Output:

Test Output

This output confirms that all ten tests (across all nested classes) passed successfully.


Next Article
Article Tags :

Similar Reads