How do I follow the Arrange-Act-Assert pattern in unit tests?

The Arrange-Act-Assert pattern is a simple way to organize unit tests so they are easy to read and understand.

A unit test usually answers three questions:

  1. Arrange: What data or objects do I need?
  2. Act: What behavior am I testing?
  3. Assert: What result do I expect?

Basic Structure

@Test
void methodName_expectedBehavior() {
    // Arrange
    // Prepare objects, input values, mocks, or test data

    // Act
    // Call the method being tested

    // Assert
    // Verify the result
}

Example with JUnit 5

import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    @Test
    void addReturnsSumOfTwoNumbers() {
        // Arrange
        Calculator calculator = new Calculator();
        int firstNumber = 2;
        int secondNumber = 3;

        // Act
        int result = calculator.add(firstNumber, secondNumber);

        // Assert
        assertEquals(5, result);
    }
}

In this test:

  • Arrange creates the Calculator and input values.
  • Act calls calculator.add(2, 3).
  • Assert checks that the result is 5.

1. Arrange

The Arrange section prepares everything the test needs.

This can include:

  • Creating the object being tested
  • Creating input values
  • Preparing test data
  • Configuring mocks
  • Setting expected values

Example:

// Arrange
Calculator calculator = new Calculator();
int firstNumber = 2;
int secondNumber = 3;
int expectedResult = 5;

For repeated setup, you can use @BeforeEach:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addReturnsSumOfTwoNumbers() {
        // Arrange
        int firstNumber = 2;
        int secondNumber = 3;

        // Act
        int result = calculator.add(firstNumber, secondNumber);

        // Assert
        assertEquals(5, result);
    }
}

Here, @BeforeEach handles common arrangement before every test.


2. Act

The Act section performs the action you want to test.

Usually, this should be one clear method call:

// Act
int result = calculator.add(2, 3);

Try to keep the Act section small. If a test performs many actions, it may be testing too much at once.


3. Assert

The Assert section checks the result.

Examples:

assertEquals(5, result);
assertTrue(result > 0);
assertNotNull(result);

If you need to verify several related results, you can use assertAll():

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

class UserTest {

    @Test
    void constructorCreatesUserWithExpectedValues() {
        // Arrange
        String name = "Alice";
        int age = 25;
        String email = "[email protected]";

        // Act
        User user = new User(name, age, email);

        // Assert
        assertAll("user details",
                () -> assertEquals("Alice", user.getName()),
                () -> assertEquals(25, user.getAge()),
                () -> assertEquals("[email protected]", user.getEmail())
        );
    }
}

Example with a Service

import org.junit.jupiter.api.Test;

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

class DiscountServiceTest {

    @Test
    void calculateDiscountReturnsTenPercentForPremiumCustomer() {
        // Arrange
        DiscountService discountService = new DiscountService();
        Customer customer = new Customer("Alice", true);
        double orderTotal = 100.00;

        // Act
        double discount = discountService.calculateDiscount(customer, orderTotal);

        // Assert
        assertEquals(10.00, discount);
    }
}

Example with Mockito

When using mocks, the mock configuration belongs in the Arrange section.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class OrderServiceTest {

    @Test
    void calculateTotalReturnsPriceFromRepository() {
        // Arrange
        ProductRepository productRepository = mock(ProductRepository.class);
        OrderService orderService = new OrderService(productRepository);

        when(productRepository.findPriceById(1L)).thenReturn(25.00);

        // Act
        double total = orderService.calculateTotal(1L, 2);

        // Assert
        assertEquals(50.00, total);
    }
}

Good Test Naming Helps AAA

A good test name should describe the expected behavior:

@Test
void addReturnsSumOfTwoNumbers() {
}

Other examples:

@Test
void withdrawReducesAccountBalance() {
}

@Test
void loginFailsWhenPasswordIsInvalid() {
}

@Test
void calculateTotalAppliesDiscountForPremiumCustomer() {
}

Readable test names make the Arrange-Act-Assert flow easier to understand.


Common Mistakes

1. Mixing Act and Assert

Avoid this:

assertEquals(5, calculator.add(2, 3));

This is short, but the action is hidden inside the assertion.

Prefer:

int result = calculator.add(2, 3);

assertEquals(5, result);

This makes the tested behavior easier to see.


2. Doing Too Much in One Test

Avoid testing several behaviors in one test:

@Test
void calculatorOperationsWork() {
    Calculator calculator = new Calculator();

    assertEquals(5, calculator.add(2, 3));
    assertEquals(1, calculator.subtract(3, 2));
    assertEquals(6, calculator.multiply(2, 3));
}

Prefer separate tests:

@Test
void addReturnsSum() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.add(2, 3);

    // Assert
    assertEquals(5, result);
}

@Test
void subtractReturnsDifference() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.subtract(3, 2);

    // Assert
    assertEquals(1, result);
}

3. Putting Assertions in Setup Methods

Avoid placing assertions in @BeforeEach. Setup methods should prepare test state.

@BeforeEach
void setUp() {
    calculator = new Calculator();
}

Then assert behavior inside test methods:

@Test
void calculatorIsCreated() {
    assertNotNull(calculator);
}

Quick Checklist

Use this checklist when writing a unit test:

  • Arrange: Did I prepare the object, inputs, expected values, and mocks?
  • Act: Did I call the method or behavior being tested?
  • Assert: Did I verify the expected result?
  • Is the test focused on one behavior?
  • Can the test run independently?
  • Is the test name descriptive?

Summary

The Arrange-Act-Assert pattern keeps tests clean and readable:

@Test
void testName() {
    // Arrange
    TestObject object = new TestObject();

    // Act
    Result result = object.doSomething();

    // Assert
    assertEquals(expectedResult, result);
}

Use Arrange to prepare, Act to execute, and Assert to verify. This structure helps you write unit tests that are easier to read, debug, and maintain.

How do I write clean and readable JUnit tests?

Clean JUnit tests should be easy to understand, focused on one behavior, and safe to change. A good test should tell you:

  1. What scenario is being tested
  2. What action happens
  3. What result is expected

Most Java projects today should use JUnit 5, from the org.junit.jupiter package.


1. Use Clear Test Names

A test name should describe the behavior being tested.

Prefer names like:

@Test
void depositIncreasesAccountBalance() {
    // test body
}

Instead of vague names like:

@Test
void testDeposit() {
    // test body
}

Good test names often follow this style:

methodName_expectedResult_whenCondition

Example:

@Test
void withdrawThrowsExceptionWhenBalanceIsInsufficient() {
    // test body
}

Or a more natural sentence style:

@Test
void cannotWithdrawMoreThanCurrentBalance() {
    // test body
}

2. Follow the Arrange-Act-Assert Pattern

A readable test usually has three sections:

Section Purpose
Arrange Prepare test data and objects
Act Execute the behavior being tested
Assert Verify the result

Example:

import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    @Test
    void addReturnsSumOfTwoNumbers() {
        // Arrange
        Calculator calculator = new Calculator();

        // Act
        int result = calculator.add(2, 3);

        // Assert
        assertEquals(5, result);
    }
}

You do not always need the comments, especially if the test is already short and obvious. But the structure should still be clear.


3. Test One Behavior Per Test

Each test should focus on one specific behavior.

Good:

@Test
void addReturnsSumOfTwoPositiveNumbers() {
    Calculator calculator = new Calculator();

    int result = calculator.add(2, 3);

    assertEquals(5, result);
}

Avoid testing many unrelated things in one test:

@Test
void calculatorWorks() {
    Calculator calculator = new Calculator();

    assertEquals(5, calculator.add(2, 3));
    assertEquals(1, calculator.subtract(3, 2));
    assertEquals(6, calculator.multiply(2, 3));
}

That test is harder to diagnose when it fails.


4. Use Meaningful Test Data

Avoid unclear values like this:

Order order = new Order("A", 1, 2, true);

Prefer readable values:

Order order = new Order("BOOK-001", 2, 19.99, true);

Even better, use helper methods when object creation is noisy:

@Test
void calculatesTotalPriceForOrder() {
    Order order = orderWithItems(
            item("Book", 2, 19.99),
            item("Pen", 1, 2.50)
    );

    BigDecimal total = order.calculateTotal();

    assertEquals(new BigDecimal("42.48"), total);
}

Readable test data makes the expected behavior easier to understand.


5. Avoid Logic in Tests

Tests should be simple. Avoid loops, conditionals, and complex calculations unless they are truly necessary.

Avoid:

@Test
void calculatesDiscount() {
    double expected = 100 * 0.9;

    assertEquals(expected, discountService.applyDiscount(100), 0.001);
}

Better:

@Test
void appliesTenPercentDiscount() {
    double result = discountService.applyDiscount(100);

    assertEquals(90.0, result, 0.001);
}

The expected value should usually be explicit. If the test calculates the expected value using similar logic to the production code, it may repeat the same bug.


6. Use @BeforeEach for Shared Setup

If several tests need the same object, create it in a @BeforeEach method.

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addReturnsSumOfTwoNumbers() {
        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }

    @Test
    void subtractReturnsDifferenceOfTwoNumbers() {
        int result = calculator.subtract(10, 4);

        assertEquals(6, result);
    }
}

Use @BeforeEach for setup that is common and improves readability.

Do not hide important test details in setup. If a value matters for understanding a specific test, keep it inside that test.


7. Keep Assertions Clear

Use the most specific assertion available.

Prefer:

assertEquals(3, items.size());
assertTrue(items.contains("Book"));
assertNotNull(user);

Instead of:

assertTrue(items.size() == 3);
assertTrue(user != null);

Specific assertions usually produce better failure messages.


8. Add Assertion Messages When Helpful

JUnit assertions can include a failure message.

assertEquals(100, account.getBalance(), "Account balance should increase after deposit");

Use messages when they clarify the business expectation. Avoid messages that simply repeat the assertion.

Less useful:

assertEquals(100, balance, "Balance should be 100");

More useful:

assertEquals(100, balance, "Initial promotional credit should be applied to new accounts");

9. Group Related Assertions with assertAll

If you need to verify several independent properties of the same result, use assertAll.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

class UserTest {

    @Test
    void createsUserProfile() {
        User user = new User("Alice", 25, "[email protected]");

        assertAll("user profile",
                () -> assertEquals("Alice", user.getName()),
                () -> assertEquals(25, user.getAge()),
                () -> assertEquals("[email protected]", user.getEmail())
        );
    }
}

This makes the test output more useful because JUnit reports all failures in the group instead of stopping at the first one.

Use assertAll when assertions are independent. If one assertion must pass before another is safe, keep it separate.


10. Test Exceptions Clearly

Use assertThrows for expected exceptions.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class BankAccountTest {

    @Test
    void withdrawThrowsExceptionWhenAmountExceedsBalance() {
        BankAccount account = new BankAccount(100);

        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> account.withdraw(150)
        );

        assertEquals("Insufficient balance", exception.getMessage());
    }
}

This clearly shows:

  • The expected exception type
  • The operation that should fail
  • Optional verification of the exception message

11. Avoid Overusing Mocks

Mocks are useful, especially for services, repositories, external APIs, and slow dependencies. But too many mocks can make tests brittle.

Prefer real objects when they are simple and fast.

Good mock use:

@Test
void sendsWelcomeEmailAfterRegistration() {
    EmailSender emailSender = mock(EmailSender.class);
    UserRepository userRepository = mock(UserRepository.class);
    RegistrationService service = new RegistrationService(userRepository, emailSender);

    service.register("[email protected]");

    verify(emailSender).sendWelcomeEmail("[email protected]");
}

Avoid mocking simple data objects:

User user = mock(User.class);
when(user.getEmail()).thenReturn("[email protected]");

Usually better:

User user = new User("[email protected]");

12. Keep Tests Independent

Each test should be able to run:

  • Alone
  • In any order
  • Repeatedly
  • Without depending on previous tests

Avoid shared mutable state between tests.

Risky:

private static List<String> users = new ArrayList<>();

Better:

private List<String> users;

@BeforeEach
void setUp() {
    users = new ArrayList<>();
}

13. Prefer One Clear Assertion Target

A test can contain multiple assertions, but they should usually verify the same behavior or outcome.

Good:

@Test
void createsActiveUserWithDefaultRole() {
    User user = userService.createUser("[email protected]");

    assertAll("created user",
            () -> assertEquals("[email protected]", user.getEmail()),
            () -> assertEquals("USER", user.getRole()),
            () -> assertTrue(user.isActive())
    );
}

Less clear:

@Test
void createUserAlsoUpdatesAuditLogAndSendsEmailAndCreatesProfile() {
    // too many responsibilities in one test
}

If a test has too many reasons to fail, split it.


14. Use Parameterized Tests for Similar Cases

If you are testing the same behavior with different inputs, use parameterized tests.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

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

class CalculatorTest {

    @ParameterizedTest
    @CsvSource({
            "1, 2, 3",
            "5, 7, 12",
            "-1, 1, 0"
    })
    void addReturnsSum(int left, int right, int expected) {
        Calculator calculator = new Calculator();

        int result = calculator.add(left, right);

        assertEquals(expected, result);
    }
}

This avoids repeating nearly identical tests.


15. Keep Test Classes Organized

A common structure is:

class SomeServiceTest {

    // fields

    // setup methods

    // tests

    // helper methods
}

Example:

class PriceCalculatorTest {

    private PriceCalculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new PriceCalculator();
    }

    @Test
    void appliesDiscountForPremiumCustomer() {
        // test
    }

    @Test
    void doesNotApplyDiscountForRegularCustomer() {
        // test
    }

    private Customer premiumCustomer() {
        return new Customer("Alice", CustomerType.PREMIUM);
    }
}

Keep helper methods at the bottom so the actual test cases remain prominent.


16. Use AssertJ or Hamcrest for Fluent Assertions When Appropriate

JUnit assertions are enough for many tests, but fluent assertion libraries can improve readability for complex objects or collections.

With AssertJ:

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class ProductServiceTest {

    @Test
    void returnsOnlyActiveProducts() {
        List<Product> products = productService.findActiveProducts();

        assertThat(products)
                .extracting(Product::getName)
                .containsExactly("Book", "Pen");
    }
}

This can be more readable than several separate collection assertions.


17. Avoid Testing Implementation Details

Test behavior, not private methods or internal steps.

Avoid writing tests that depend on exactly how a method is implemented. Instead, verify observable results.

Prefer:

@Test
void calculatesFinalPriceIncludingTax() {
    Money finalPrice = priceService.calculateFinalPrice(new Money("100.00"));

    assertEquals(new Money("120.00"), finalPrice);
}

Avoid:

@Test
void callsTaxCalculatorBeforeDiscountCalculator() {
    // brittle unless call order is part of the business contract
}

If you refactor the internals without changing behavior, tests should still pass.


18. Make Failures Easy to Diagnose

A failing test should quickly answer:

  • What behavior failed?
  • What input caused it?
  • What was expected?
  • What actually happened?

For example:

@Test
void appliesFreeShippingForOrdersOverOneHundredDollars() {
    Order order = orderWithTotal(new BigDecimal("120.00"));

    ShippingCost cost = shippingService.calculateShipping(order);

    assertEquals(ShippingCost.free(), cost);
}

This is much easier to understand than:

@Test
void test1() {
    assertEquals(0, service.calc(new BigDecimal("120.00")));
}

Quick Checklist

Use this checklist when reviewing JUnit tests:

  • Clear test name
  • Arrange-Act-Assert structure
  • One behavior per test
  • Readable test data
  • No unnecessary logic in the test
  • Specific assertions
  • Independent tests
  • Minimal shared state
  • Mocks only where useful
  • Parameterized tests for repeated cases
  • No testing of implementation details
  • Helpful failure messages when needed

A clean JUnit test should read almost like documentation for the behavior of your code.

How do I organize tests with nested test classes?

Organizing Tests with Nested Test Classes

Nested test classes are a great way to group related tests inside a single test class. In Java, this is commonly done with JUnit 5 using @Nested.

They help you structure tests around:

  • A specific method
  • A scenario
  • A state of the object under test
  • Success vs failure cases
  • Different user roles or permissions
  • Different validation paths

Basic Example

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    private final Calculator calculator = new Calculator();

    @Nested
    class AddTests {

        @Test
        void shouldAddTwoPositiveNumbers() {
            int result = calculator.add(2, 3);

            assertEquals(5, result);
        }

        @Test
        void shouldAddNegativeNumbers() {
            int result = calculator.add(-2, -3);

            assertEquals(-5, result);
        }
    }

    @Nested
    class DivideTests {

        @Test
        void shouldDivideTwoNumbers() {
            int result = calculator.divide(10, 2);

            assertEquals(5, result);
        }

        @Test
        void shouldThrowWhenDividingByZero() {
            // test exception case here
        }
    }
}

A Common Organization Style

A useful pattern is:

class UserServiceTest {

    @Nested
    class CreateUser {

        @Test
        void shouldCreateUserWhenInputIsValid() {
        }

        @Test
        void shouldRejectDuplicateEmail() {
        }

        @Test
        void shouldRejectInvalidEmail() {
        }
    }

    @Nested
    class UpdateUser {

        @Test
        void shouldUpdateUserWhenUserExists() {
        }

        @Test
        void shouldThrowWhenUserDoesNotExist() {
        }
    }

    @Nested
    class DeleteUser {

        @Test
        void shouldDeleteUserWhenUserExists() {
        }

        @Test
        void shouldDoNothingWhenUserDoesNotExist() {
        }
    }
}

This makes the test report easier to read:

UserServiceTest
 ├─ CreateUser
 │   ├─ shouldCreateUserWhenInputIsValid
 │   ├─ shouldRejectDuplicateEmail
 │   └─ shouldRejectInvalidEmail
 ├─ UpdateUser
 │   ├─ shouldUpdateUserWhenUserExists
 │   └─ shouldThrowWhenUserDoesNotExist
 └─ DeleteUser
     ├─ shouldDeleteUserWhenUserExists
     └─ shouldDoNothingWhenUserDoesNotExist

Using @BeforeEach in Nested Classes

Each nested class can have its own setup.

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class OrderServiceTest {

    private OrderService orderService;

    @BeforeEach
    void setUp() {
        orderService = new OrderService();
    }

    @Nested
    class WhenOrderIsNew {

        private Order order;

        @BeforeEach
        void setUp() {
            order = new Order("NEW");
        }

        @Test
        void shouldAllowCancellation() {
            // test new order cancellation
        }

        @Test
        void shouldAllowPayment() {
            // test new order payment
        }
    }

    @Nested
    class WhenOrderIsShipped {

        private Order order;

        @BeforeEach
        void setUp() {
            order = new Order("SHIPPED");
        }

        @Test
        void shouldNotAllowCancellation() {
            // test shipped order cancellation
        }
    }
}

The outer @BeforeEach runs before the nested class @BeforeEach.

Execution order is:

OrderServiceTest.setUp()
WhenOrderIsNew.setUp()
test method

Recommended Naming Styles

Option 1: Method-based grouping

class ProductServiceTest {

    @Nested
    class FindById {

        @Test
        void shouldReturnProductWhenFound() {
        }

        @Test
        void shouldThrowWhenProductDoesNotExist() {
        }
    }

    @Nested
    class Save {

        @Test
        void shouldSaveValidProduct() {
        }

        @Test
        void shouldRejectProductWithoutName() {
        }
    }
}

Option 2: Scenario-based grouping

class CheckoutServiceTest {

    @Nested
    class WhenCartIsEmpty {

        @Test
        void shouldRejectCheckout() {
        }
    }

    @Nested
    class WhenCartHasItems {

        @Test
        void shouldCreateOrder() {
        }

        @Test
        void shouldClearCartAfterCheckout() {
        }
    }
}

Both are valid. For service classes, I usually prefer method-based grouping. For complex domain behavior, scenario-based grouping often reads better.


Best Practices

Do

  • Use @Nested to group related test cases.
  • Keep nesting shallow, usually one level deep.
  • Use descriptive nested class names.
  • Use @BeforeEach inside nested classes for scenario-specific setup.
  • Keep test method names behavior-focused.

Example:

@Test
void shouldThrowExceptionWhenEmailAlreadyExists() {
}

Avoid

  • Deep nesting like 3–4 levels unless absolutely necessary.
  • Putting unrelated tests in the same nested class.
  • Sharing too much mutable state between nested classes.
  • Making nested class names vague, such as Tests1 or Validations.

With Spring Boot / Spring MVC Tests

Nested tests also work with Spring test annotations.

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Nested
    class CreateUser {

        @Test
        void shouldReturnCreatedWhenRequestIsValid() throws Exception {
            // mockMvc.perform(...)
        }

        @Test
        void shouldReturnBadRequestWhenEmailIsInvalid() throws Exception {
            // mockMvc.perform(...)
        }
    }

    @Nested
    class GetUser {

        @Test
        void shouldReturnUserWhenFound() throws Exception {
            // mockMvc.perform(...)
        }

        @Test
        void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
            // mockMvc.perform(...)
        }
    }
}

Rule of Thumb

Use nested test classes when your flat test class starts looking like this:

@Test
void createUserShouldSucceedWhenInputIsValid() {
}

@Test
void createUserShouldFailWhenEmailExists() {
}

@Test
void updateUserShouldSucceedWhenUserExists() {
}

@Test
void updateUserShouldFailWhenUserDoesNotExist() {
}

Refactor it into:

class UserServiceTest {

    @Nested
    class CreateUser {
    }

    @Nested
    class UpdateUser {
    }
}

That usually makes the tests easier to scan, maintain, and understand.

How do I add display names to JUnit tests?

In JUnit 5, you can add readable names to your tests using the @DisplayName annotation.

@DisplayName lets you show a friendly, human-readable test name in test reports and IDE test runners instead of relying only on the Java method name.

Example

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

class CalculatorTest {

    @Test
    @DisplayName("Adding two positive numbers returns their sum")
    void shouldAddTwoPositiveNumbers() {
        int result = 2 + 3;

        assertEquals(5, result);
    }
}

The method name is still:

shouldAddTwoPositiveNumbers

But the test report can display:

Adding two positive numbers returns their sum

Add Display Names to Test Classes

You can also add @DisplayName to the test class itself:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

@DisplayName("Password validation tests")
class PasswordValidatorTest {

    @Test
    @DisplayName("Valid password should be accepted")
    void shouldAcceptValidPassword() {
        assertTrue(true);
    }

    @Test
    @DisplayName("Short password should be rejected")
    void shouldRejectShortPassword() {
        assertTrue(true);
    }
}

Using Emojis or Symbols

JUnit 5 display names can include spaces, punctuation, and even emojis:

@Test
@DisplayName("✅ Valid email should pass validation")
void shouldAcceptValidEmail() {
    assertTrue(true);
}

Use this carefully. Emojis can make reports more readable, but they may not be appropriate for every team or CI environment.

Display Names for Nested Tests

@DisplayName is especially useful with @Nested tests:

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;

@DisplayName("Shopping cart")
class ShoppingCartTest {

    @Nested
    @DisplayName("when adding items")
    class AddingItems {

        @Test
        @DisplayName("updates the total item count")
        void shouldUpdateItemCount() {
            int itemCount = 1 + 2;

            assertEquals(3, itemCount);
        }
    }
}

This can produce a readable test structure such as:

Shopping cart
 └─ when adding items
    └─ updates the total item count

Display Names for Parameterized Tests

For parameterized tests, you can customize each invocation name:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

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

class NumberTest {

    @ParameterizedTest(name = "{0} should be positive")
    @ValueSource(ints = {1, 5, 10})
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }
}

This can display:

1 should be positive
5 should be positive
10 should be positive

You can also combine it with @DisplayName:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

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

class NumberTest {

    @DisplayName("Positive number validation")
    @ParameterizedTest(name = "Value {0} is positive")
    @ValueSource(ints = {1, 5, 10})
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }
}

Required Dependency

Make sure you are using JUnit Jupiter, which is JUnit 5:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.13.4</version>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'

test {
    useJUnitPlatform()
}

Summary

Use @DisplayName when you want test output to be more readable:

@Test
@DisplayName("User should be created when input is valid")
void shouldCreateUserWhenInputIsValid() {
}

The Java method name remains valid and searchable, while the test report shows a clearer description.

How do I disable a test with @Disabled annotation?

In JUnit 5, you can disable a test by annotating it with @Disabled from the org.junit.jupiter.api package.

Disable a Single Test Method

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    @Disabled
    void divisionByZeroTest() {
        // This test will be skipped
    }
}

When you run the test suite, this test will not be executed.

Add a Reason

It is a good practice to include a reason so other developers know why the test is disabled.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    @Disabled("Temporarily disabled until division-by-zero handling is fixed")
    void divisionByZeroTest() {
        // This test will be skipped
    }
}

Disable an Entire Test Class

You can also place @Disabled on a test class to skip all tests inside it.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled("Disabled while refactoring the calculator module")
class CalculatorTest {

    @Test
    void additionTest() {
        // This test will be skipped
    }

    @Test
    void subtractionTest() {
        // This test will also be skipped
    }
}

Summary

Use:

@Disabled

or preferably:

@Disabled("Reason why this test is disabled")

The required import is:

import org.junit.jupiter.api.Disabled;

@Disabled is useful for temporarily skipping tests that are incomplete, unstable, or depend on functionality that is not ready yet.