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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.