What you'll learn
How do you know your code works — and keeps working after every change? You write automated tests. This module introduces JUnit 5, the standard testing framework for Java, and the habits that separate hobby code from professional software.
By the end you'll be able to:
- Explain why automated testing matters and the levels of testing
- Write and run tests with JUnit 5 using the Arrange-Act-Assert pattern
- Use assertions, lifecycle methods, and exception tests
- Run one test over many inputs with parameterized tests
- Isolate code under test with mocking (Mockito)
- Understand test-driven development (TDD)
Why test?
Manual testing ("run it and see") doesn't scale and doesn't catch regressions — bugs a new change reintroduces into old code. Automated tests run in seconds, document how your code should behave, and give you the confidence to refactor. Tests come in levels, and a healthy suite has far more fast unit tests than slow end-to-end ones:
Your first test
A JUnit test is a method annotated with @Test. Structure it with the Arrange-Act-Assert pattern for clarity — set things up, do the thing, then check the result:
Arrange
Set up the objects and inputs
Act
Call the method under test
Assert
Check the result is what you expect
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class Calculator {
int add(int a, int b) { return a + b; }
}
class CalculatorTest {
@Test
void addsTwoNumbers() {
Calculator calc = new Calculator(); // Arrange
int result = calc.add(2, 3); // Act
assertEquals(5, result); // Assert
}
}Assertions
An assertion is the check that decides pass or fail — if it's false, the test fails with a helpful message. JUnit's Assertions class has one for every situation:
assertEquals(5, calc.add(2, 3)); // equal?
assertTrue(calc.add(2, 3) > 0); // condition true?
assertFalse("hi".isBlank()); // condition false?
assertNull(cache.get("missing")); // is null?
assertNotNull(user.getName()); // is not null?
assertThrows(ArithmeticException.class,
() -> { int x = 1 / 0; }); // does it throw?Tip
assertThrows takes a lambda — it runs the code and passes only if the expected exception is thrown. Prefer specific assertions (assertEquals) over generic ones (assertTrue(a == b)): the failure messages are far more useful.Lifecycle & exceptions
Tests should be independent — each starting from a clean slate. Lifecycle annotations run setup and cleanup around your tests: @BeforeEach/@AfterEach around every test, and @BeforeAll/@AfterAll once for the whole class:
class OrderServiceTest {
OrderService service;
@BeforeEach // runs before EVERY test
void setUp() {
service = new OrderService();
}
@Test
void placesOrder() { /* uses a fresh service */ }
@AfterEach // runs after EVERY test (cleanup)
void tearDown() { /* close resources */ }
}Parameterized tests
When you want to test the same logic against many inputs, don't copy-paste the test — use a parameterized test. JUnit runs it once per value:
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 100, -8})
void isEven(int number) {
assertEquals(0, number % 2); // one test, run 5 times
}Test doubles & mocking
A true unit test isolates one class. But classes have collaborators — a database, an API, a payment gateway — that are slow or have side effects. A mock is a fake stand-in you control: you tell it what to return and verify how it was used. Mockito is the go-to library:
// Mockito creates a fake collaborator you fully control
PaymentGateway gateway = mock(PaymentGateway.class);
when(gateway.charge(100)).thenReturn(true);
OrderService service = new OrderService(gateway);
boolean ok = service.checkout(100);
assertTrue(ok);
verify(gateway).charge(100); // confirm it was calledNote
Test-driven development
TDD flips the usual order into a short loop: Red (write a failing test for the behaviour you want) → Green (write the simplest code to pass it) → Refactor (clean up, tests still green). It keeps you focused, guarantees coverage, and produces code that's testable by design.
Recap & quick check
Key takeaways
- Automated tests catch regressions, document behaviour, and enable fearless refactoring.
- A JUnit test is an @Test method structured as Arrange-Act-Assert.
- Assertions (assertEquals, assertThrows, …) decide pass/fail; prefer specific ones.
- @BeforeEach/@AfterEach keep tests independent; @ParameterizedTest runs one test over many inputs.
- Mock collaborators (Mockito) to isolate the unit; TDD = Red → Green → Refactor.
Quick check
1. What annotation marks a JUnit 5 test method?
2. What does the Arrange-Act-Assert pattern describe?
3. How do you verify that code throws an exception?
4. What is a mock used for?
5. What's the TDD cycle?
Great — you can now prove your code works. Next up: Module 30 — Debugging, Logging & Profiling.