Test Lifecycle & Setup
Test Lifecycle & Setup
Well-structured tests share a predictable rhythm: arrange resources before a test runs, exercise the subject under test, then clean up afterward. JUnit 5 formalises this rhythm with four lifecycle annotations: @BeforeAll, @BeforeEach, @AfterEach, and @AfterAll. Understanding exactly when each fires — and why — is the difference between a test suite that is fast, isolated, and maintainable, and one that is brittle and full of hidden state.
The Execution Order at a Glance
JUnit 5 executes these callbacks in a strict order for each test class:
@BeforeAll— runs once before the first test method in the class.@BeforeEach— runs before every test method.- Test method executes.
@AfterEach— runs after every test method.@AfterAll— runs once after the last test method in the class.
@BeforeAll and @AfterAll must therefore be static (they belong to the class, not to an instance) unless you switch to @TestInstance(Lifecycle.PER_CLASS).
@BeforeEach — Per-Test Preparation
@BeforeEach is the workhorse of test setup. Use it to build a fresh, known-good state for each test so that no test can be influenced by a previous one. Typical uses include constructing the system under test, seeding a small in-memory data structure, or opening a resource that needs to be closed after the test.
Because setUp() runs before each test, both methods start with a cart that already contains one Widget. Neither test can break the other by leaving behind stale cart state.
@AfterEach — Per-Test Teardown
@AfterEach mirrors @BeforeEach. Use it to release resources that were acquired in @BeforeEach — closing a file handle, resetting a static mock, or rolling back a database transaction. JUnit guarantees @AfterEach runs even when the test itself throws an exception, which makes it the right place for cleanup that must always happen.
AutoCloseable, consider JUnit 5's @TempDir extension (for directories) or the CloseableResource Store API instead of manual @AfterEach cleanup. They are less error-prone because the framework handles closing automatically.
@BeforeAll — One-Time Class Setup
Some fixtures are expensive to create and safe to share across tests: a database connection pool, a started server, or a compiled regex. @BeforeAll runs once per class, making it ideal for this kind of costly, read-only shared state.
Notice the pattern: @BeforeAll starts the database once; @BeforeEach resets data before each test. The expensive operation happens once; isolation is still maintained cheaply.
@BeforeAll fixture and do not reset it, later tests can fail because of earlier ones — a classic order-dependent test failure. Reserve @BeforeAll for resources that are either truly read-only from the test's perspective (a compiled regex, a connection pool) or are reset in @BeforeEach.
Static vs. @TestInstance(PER_CLASS)
With the default PER_METHOD lifecycle, @BeforeAll and @AfterAll methods must be static because no instance exists when they are called. Annotating the class with @TestInstance(TestInstance.Lifecycle.PER_CLASS) tells JUnit to create one shared instance for the whole class, removing the static requirement — handy when the fixture itself is not statically obtainable (e.g. an injected Spring ApplicationContext).
Naming and Ordering Best Practices
- Name
@BeforeEachmethodssetUp()or something descriptive likegivenFreshCart(). A descriptive name makes failing setup easier to diagnose. - Keep
@BeforeEachshort. If setup exceeds 10–15 lines it is a signal the test class is doing too much — split it. - Use
@DisplayNameon the test class and methods to write human-readable names that appear in reports without cryptic method identifiers. - Avoid putting assertions inside
@BeforeEach; setup failures already surface as errors, and assertions add noise. - If multiple
@BeforeEachmethods exist (possible via inheritance), JUnit calls the superclass method first. Rely on this to layer shared base-class setup with per-subclass refinements.
Inheritance and Shared Fixtures
A common pattern in large codebases is a base test class that handles boilerplate — starting a web client, configuring logging — and concrete subclasses that add domain-specific setup:
Summary
@BeforeEach and @AfterEach are your primary tools for test isolation — they guarantee a clean slate before every test and reliable cleanup afterward. @BeforeAll and @AfterAll optimise expensive, inherently shared fixtures that would make the suite slow if repeated per test. The golden rule is to share state only when it is truly read-only or reset in @BeforeEach. This discipline keeps your test suite fast, deterministic, and trustworthy — the foundations you will rely on in every subsequent lesson on Mockito, TDD, and best practices.