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:
- Arrange: What data or objects do I need?
- Act: What behavior am I testing?
- 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
Calculatorand 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.
