Testing with JUnit 5
@Test, core assertions, @BeforeEach, arrange-act-assert, and why unit tests exist.
Why unit tests?
A unit test isolates one piece of behaviour and asserts it is correct. Tests give you:
- Confidence to refactor: if the tests pass, behaviour is preserved.
- Executable documentation: tests show how a class is supposed to be used.
- Fast regression feedback: run mvn test after every change; failures surface immediately.
JUnit 5 (the JUnit Jupiter API) is the standard Java testing library.
Anatomy of a test class
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator(); // fresh instance before each test
}
@Test
void add_returnsSum() {
// Arrange
int a = 3, b = 4;
// Act
int result = calc.add(a, b);
// Assert
assertEquals(7, result);
}
@Test
void divide_byZero_throwsArithmeticException() {
assertThrows(ArithmeticException.class, () -> calc.divide(5, 0));
}
}
Arrange-Act-Assert (AAA) is the standard structure for a readable test:
- Arrange: set up the objects and inputs. - Act: call the method under test. - Assert: verify the outcome.
Core assertions
| Assertion | Tests |
|---|---|
| assertEquals(expected, actual) | Value equality |
| assertNotEquals(a, b) | Values differ |
| assertTrue(condition) | Condition is true |
| assertFalse(condition) | Condition is false |
| assertNull(obj) | Reference is null |
| assertNotNull(obj) | Reference is not null |
| assertThrows(ExType.class, () -> ...) | Lambda throws that exception |
Note the argument order: expected first, actual second. @BeforeEach methods run before every @Test method, resetting shared state so tests don't interfere with each other.