Why is @BeforeEach the standard way to achieve test isolation, and what are the main pitfalls of relying on @BeforeAll for shared mutable state?
answer
- Isolation = fresh known state, order-independent, parallel-safe
- @BeforeEach + new-instance-per-test = automatic reset
- @BeforeAll mutable state → order-dependent flaky tests
- Passes alone, fails in suite = shared-state smell
- Expensive resource in @BeforeAll, per-test reset in @BeforeEach
basics
~20 s@BeforeEach rebuilds the test's objects before every test, so no test sees leftovers from another — tests stay independent and can run in any order. @BeforeAll runs once and any mutable state it creates is shared, so one test can corrupt it for the next, causing order-dependent, flaky failures.
solid answer
~50 sTest isolation means each test runs against a fresh, known state so results don't depend on order or on what ran before. @BeforeEach delivers this: combined with JUnit's default new-instance-per-test lifecycle, it reconstructs the fixture for every test, so mutations don't leak. That makes tests reorderable, parallelizable, and individually runnable. @BeforeAll runs once and is for expensive shared resources, but anything mutable it sets up — a populated collection, a database with seed rows, a stateful client — becomes shared state. If one test mutates it, later tests see the change, producing flaky, order-dependent failures that pass alone but fail in a suite. The pitfalls: hidden coupling, non-deterministic outcomes, and bugs that only appear under parallel execution. Mitigations: keep @BeforeAll resources immutable or self-resetting, do per-test reset/seed in @BeforeEach, and reserve @BeforeAll for genuinely expensive, safely-shareable setup.
go deeper
Understands that @BeforeEach gives each test a fresh start so tests don't interfere.
Explains the new-instance-per-test lifecycle plus @BeforeEach, and that @BeforeAll mutable state can cause order-dependent failures.
Diagnoses flaky/order-dependent tests as shared-state leakage, designs @BeforeAll+@BeforeEach splits (e.g. transaction rollback), and weighs PER_CLASS trade-offs.
Sets suite-wide isolation policy, enables parallel execution to surface coupling, and defines patterns/base classes that make expensive shared fixtures safe at scale.
## What 'test isolation' means **Test isolation** (a.k.a. test independence) is the property that **each test runs against its own fresh, known state and leaves no trace that affects another test**. Isolated tests can be run in *any order*, *individually*, or *in parallel*, and always give the same result. The opposite is **order-dependent** or **flaky** tests: they pass when run alone but fail (or pass spuriously) when run alongside others, because they implicitly depend on state another test produced. ## How @BeforeEach achieves isolation Two JUnit 5 mechanisms combine: 1. **New instance per test (`PER_METHOD` lifecycle, the default).** JUnit creates a *fresh instance of the test class for every `@Test`*, so instance fields are reset automatically — one test can't see another's field mutations. 2. **`@BeforeEach`** does the *active* rebuild on that fresh instance: `new`-ing the object under test, recreating mocks, seeding a clean dataset. Because it runs before *every* test, the starting state is identical and pristine each time. Together they guarantee that a test's mutations are thrown away before the next test, so there's nothing to leak. That's why `@BeforeEach` is the *default, safe* place for setup. ## Why @BeforeAll is tempting — and risky `@BeforeAll` runs **once** for the whole class. It exists for **expensive** setup you don't want to repeat: starting a Testcontainers database, booting an embedded server, loading a big read-only file. The danger appears when the thing it sets up is **mutable and shared**: - Suppose `@BeforeAll` creates a `static List<String> users` and seeds it. Test A adds an entry; Test B asserts the list size. Now B's result depends on whether A ran first — **order-dependent failure**. - A shared database populated once in `@BeforeAll`: a test that inserts or deletes rows changes the state for every later test. The suite passes today and breaks tomorrow when test order or selection changes. - A stateful HTTP client or a singleton configured in `@BeforeAll`: one test flips a flag, another inherits it. These bugs are **insidious**: each test passes in isolation (so they look fine when you run them one at a time), but the *suite* is flaky. They get dramatically worse under **parallel execution**, where shared mutable state is also a data race. ## The PER_CLASS amplifier If you switch to `@TestInstance(PER_CLASS)` so `@BeforeAll` can be non-static and hold instance fields, you've made the *whole class* share one instance — every instance field is now cross-test shared state, magnifying the same hazard. PER_CLASS is fine for *read-only* shared setup but demands discipline. ## How to get both speed and isolation The standard pattern separates the two concerns: - **`@BeforeAll`**: create the *expensive, shareable, ideally read-only* resource once (start the DB/server/container). Pair it with **`@AfterAll`** to close it. - **`@BeforeEach`**: bring the *per-test* state to a clean baseline on top of that resource — truncate and re-seed tables, reset the mock, begin a transaction that `@AfterEach` rolls back. A transaction-per-test that rolls back in `@AfterEach` is a classic way to share an expensive DB while keeping each test isolated. For collections or counters, prefer rebuilding them in `@BeforeEach` rather than seeding once in `@BeforeAll`. ## Rules of thumb - Default to `@BeforeEach`; reach for `@BeforeAll` only for proven-expensive setup. - Anything a test *mutates* should be (re)initialized per test. - Keep `@BeforeAll` state immutable or self-resetting; always close its resources in `@AfterAll`. - If a test passes alone but fails in the suite, suspect shared `@BeforeAll`/static state first. - Enabling parallel test execution is a good forcing function — it surfaces hidden shared-state coupling.
- A test passes when run alone but fails when the whole class runs. What's your first hypothesis?Shared mutable state leaking between tests — typically a static field, a @BeforeAll-created resource, or a singleton that a prior test mutated. The failing test depends on state another test produced. Fix by resetting that state in @BeforeEach or making the shared resource immutable/self-resetting.
- How can you keep an expensive database started once but still isolate each test?Start/stop the DB in @BeforeAll/@AfterAll, then per test either truncate-and-reseed in @BeforeEach or run each test in a transaction that @AfterEach rolls back. The expensive resource is shared; the per-test data state is fresh.
saying these in an interview costs you the question
- Seeding mutable collections/data once in @BeforeAll and mutating them in tests
- Assuming static fields set in @BeforeAll are safe across tests
- Believing tests passing individually proves the suite is isolated
- Using PER_CLASS with mutable instance fields without per-test reset
- Ignoring that shared @BeforeAll state is a data race under parallel execution