Close

JUnit 5 - BDD Pattern with Nested Classes

[Last Updated: Dec 6, 2025]

JUnit 5's @Nested classes provide a natural way to implement the Behavior-Driven Development (BDD) "Given-When-Then" pattern. Each nested level can represent a different phase of the BDD scenario, creating highly readable and well-structured tests that closely mirror natural language specifications.

BDD with Nested Classes

By organizing tests into nested classes, you can clearly separate the setup (Given), action (When), and verification (Then) phases while maintaining the context and state across all phases. This approach makes tests self-documenting and easier to understand for both developers and non-technical stakeholders.

Example

Example app

package com.logicbig.example;

public class Product {
    private final String name;
    private final double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}
public class ShoppingCart {
    private final List<Product> products = new ArrayList<>();

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public List<Product> getProducts() {
        return new ArrayList<>(products);
    }

    public int getItemCount() {
        return products.size();
    }

    public double getTotalPrice() {
        return products.stream()
                       .mapToDouble(Product::getPrice)
                       .sum();
    }
}

BDD style test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ShoppingCartBddTest {

    @Nested
    @DisplayName("Given an empty shopping cart")
    class GivenEmptyShoppingCart {
        protected ShoppingCart cart;

        @BeforeEach
        void givenEmptyCart() {
            cart = new ShoppingCart();
        }

        @Nested
        @DisplayName("When a product is added")
        class WhenProductAdded {
            protected Product product;

            @BeforeEach
            void whenProductAdded() {
                product = new Product("Laptop", 999.99);
                cart.addProduct(product);
            }

            @Test
            @DisplayName("Then cart should contain the product")
            void cartShouldContainProduct() {
                assertTrue(cart.getProducts().contains(product));
                assertEquals(1, cart.getItemCount());
            }

            @Test
            @DisplayName("Then total price should be product price")
            void totalPriceShouldBeCorrect() {
                assertEquals(999.99, cart.getTotalPrice(), 0.001);
            }
        }
    }

    @Nested
    @DisplayName("Given a shopping cart with one product")
    class GivenCartWithOneProduct {
        protected ShoppingCart cart;
        protected Product product;

        @BeforeEach
        void givenCartWithProduct() {
            cart = new ShoppingCart();
            product = new Product("Mouse", 29.99);
            cart.addProduct(product);
        }

        @Nested
        @DisplayName("When another product is added")
        class WhenAnotherProductAdded {
            protected Product secondProduct;

            @BeforeEach
            void whenAnotherProductAdded() {
                secondProduct = new Product("Keyboard", 79.99);
                cart.addProduct(secondProduct);
            }

            @Test
            @DisplayName("Then cart should have two items")
            void shouldHaveTwoItems() {
                assertEquals(2, cart.getItemCount());
            }

            @Test
            @DisplayName("Then total price should be sum of both products")
            void totalPriceShouldBeSum() {
                assertEquals(109.98, cart.getTotalPrice(), 0.001);
            }
        }

        @Nested
        @DisplayName("When the product is removed")
        class WhenProductRemoved {

            @BeforeEach
            void whenProductRemoved() {
                cart.removeProduct(product);
            }

            @Test
            @DisplayName("Then cart should be empty")
            void cartShouldBeEmpty() {
                assertTrue(cart.getProducts().isEmpty());
                assertEquals(0, cart.getItemCount());
            }

            @Test
            @DisplayName("Then total price should be zero")
            void totalPriceShouldBeZero() {
                assertEquals(0.0, cart.getTotalPrice(), 0.001);
            }
        }
    }
}
mvn test -Dtest=ShoppingCartBddTest

Output

D:\example-projects\junit-5\nested-classes\junit-5-nested-classes-bdd-pattern>mvn test -Dtest=ShoppingCartBddTest
[INFO] Scanning for projects...
[INFO]
[INFO] ----------< com.logicbig.example:nested-classes-bdd-pattern >-----------
[INFO] Building nested-classes-bdd-pattern 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ nested-classes-bdd-pattern ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-classes-bdd-pattern\src\main\resources
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ nested-classes-bdd-pattern ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ nested-classes-bdd-pattern ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-classes-bdd-pattern\src\test\resources
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ nested-classes-bdd-pattern ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.5.4:test (default-test) @ nested-classes-bdd-pattern ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] +--When a product is added - 0.058 ss
[INFO] | +-- [OK] Then total price should be product price - 0.037 ss
[INFO] | '-- [OK] Then cart should contain the product - 0.004 ss
[INFO] +--When another product is added - 0.011 ss
[INFO] | +-- [OK] Then cart should have two items - 0.004 ss
[INFO] | '-- [OK] Then total price should be sum of both products - 0.003 ss
[INFO] +--When the product is removed - 0.016 ss
[INFO] | +-- [OK] Then total price should be zero - 0.002 ss
[INFO] | '-- [OK] Then cart should be empty - 0.004 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.105 s
[INFO] Finished at: 2025-12-06T15:13:27+08:00
[INFO] ------------------------------------------------------------------------

Example Project

Dependencies and Technologies Used:

  • junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
     Version Compatibility: 5.0.0 - 6.0.1Version List
    ×

    Version compatibilities of junit-jupiter-engine with this example:

    • 5.0.0
    • 5.0.1
    • 5.0.2
    • 5.0.3
    • 5.1.0
    • 5.1.1
    • 5.2.0
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.4.0
    • 5.4.1
    • 5.4.2
    • 5.5.0
    • 5.5.1
    • 5.5.2
    • 5.6.0
    • 5.6.1
    • 5.6.2
    • 5.6.3
    • 5.7.0
    • 5.7.1
    • 5.7.2
    • 5.8.0
    • 5.8.1
    • 5.8.2
    • 5.9.0
    • 5.9.1
    • 5.9.2
    • 5.9.3
    • 5.10.0
    • 5.10.1
    • 5.10.2
    • 5.10.3
    • 5.10.4
    • 5.10.5
    • 5.11.0
    • 5.11.1
    • 5.11.2
    • 5.11.3
    • 5.11.4
    • 5.12.0
    • 5.12.1
    • 5.12.2
    • 5.13.0
    • 5.13.1
    • 5.13.2
    • 5.13.3
    • 5.13.4
    • 5.14.0
    • 5.14.1
    • 6.0.0
    • 6.0.1

    Versions in green have been tested.

  • JDK 25
  • Maven 3.9.11

BDD Pattern with Nested Classes Select All Download
  • junit-5-nested-classes-bdd-pattern
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example
                • ShoppingCartBddTest.java

    See Also