skip to content

What is the default JUnit 5 test instance lifecycle, and why does JUnit create a brand-new instance of the test class for each @Test method?

level: juniorimportance: must knowfreq 55%

answer

  1. PER_METHOD = new instance per @Test = isolation
  2. Default lifecycle in JUnit 5
  3. Instance fields reset every test
  4. @BeforeAll/@AfterAll must be static here
  5. Order-independence comes for free

basics

~20 s

By default JUnit 5 makes a fresh copy of your test class for every @Test method (PER_METHOD). This keeps tests isolated: fields set by one test can't leak into another, so test order doesn't matter.

solid answer

~40 s

JUnit 5's default lifecycle is PER_METHOD: before running each @Test, JUnit constructs a new instance of the test class, so every test starts from the class's initial field state. This guarantees isolation — a value one test stores in an instance field cannot affect another test, which is why well-written JUnit tests can run in any order and in parallel without interfering. @BeforeEach/@AfterEach run around each method on that fresh instance to set up and tear down per-test state. The cost is that anything expensive built per instance is rebuilt for every test; if you truly need to share one object across all tests you either make it static (and use @BeforeAll/@AfterAll) or opt into PER_CLASS. Relying on field state surviving between tests is a bug, not a feature.

code

java · 15 lines
java
class CounterTest {
    int count = 0; // a fresh instance field per test

    @Test
    void firstTest() {
        count++;
        assertEquals(1, count); // passes
    }

    @Test
    void secondTest() {
        // brand-new instance: count is 0 again, NOT 1
        assertEquals(0, count); // passes
    }
}

go deeper

for a junior

Knows the default makes a new instance per test and that this gives isolation; can name @BeforeEach/@AfterEach.

for a middle

Explains why instance fields reset, why @BeforeAll must be static here, and ties isolation to order-independence and parallel safety.

for a senior

Articulates the isolation-vs-cost trade-off, recognizes reliance on cross-test field state as a defect, and knows when to escalate to static/PER_CLASS.

for a principal

Frames lifecycle choice as a test-suite design decision (determinism, parallelism, fixture cost) and sets team conventions and CI ordering policies accordingly.

## What 'test lifecycle' means A **test class** in JUnit 5 (the modern testing framework for Java, sometimes called JUnit Jupiter) is just an ordinary class whose methods are annotated with `@Test`. A **test instance** is an object of that class that JUnit creates in order to run a test. The **lifecycle** is the policy JUnit uses to decide *how many* such instances it creates and *when*. ## The default: PER_METHOD By default JUnit uses `TestInstance.Lifecycle.PER_METHOD`. This means: **for every single `@Test` method, JUnit constructs a new object of the test class.** If your class has 5 test methods, JUnit creates 5 separate instances over the run — one per test — each via the class's constructor. Because each test gets its own object, each test's **instance fields** (non-static fields declared in the class) start at whatever the field initializers / constructor set them to. Nothing a previous test wrote into a field can be seen by the next test, because the next test runs on a *different object*. ## Why JUnit does this: isolation The whole point is **test isolation**. A good unit test should produce the same result no matter what other tests ran before it, and no matter the order. If tests shared one object, then test A could leave a counter at 3, and test B — expecting it at 0 — would fail or pass depending on ordering. That makes tests flaky and order-dependent, which defeats the purpose of automated testing. By giving every test a fresh instance, JUnit makes accidental state-sharing through instance fields impossible. ## Where the lifecycle hooks fit Four key annotations attach setup/teardown code: - `@BeforeEach` — runs *before each* test method, on the fresh instance. Use it to build the object-under-test, reset collaborators, etc. - `@AfterEach` — runs *after each* test method. Use it to clean up per-test resources. - `@BeforeAll` / `@AfterAll` — run *once* for the whole class, before any / after all tests. In PER_METHOD mode these **must be `static`**, because there is no single shared instance to call them on — JUnit invokes them at the class level. Sequence for two tests in PER_METHOD looks like: ``` @BeforeAll (static) new TestClass() ; @BeforeEach ; test1 ; @AfterEach new TestClass() ; @BeforeEach ; test2 ; @AfterEach @AfterAll (static) ``` ## Consequences to remember - **Don't rely on field state carrying over** between tests — it won't, and depending on it is a bug. - **Cost:** anything constructed per instance (in the constructor or `@BeforeEach`) is rebuilt for every test. Usually cheap; occasionally (heavy fixtures) you want to share — that's what `@BeforeAll`/static, or PER_CLASS, are for. - Fresh instances are also what makes safe **parallel** and **shuffled-order** execution possible. ## Contrast (one sentence) The alternative, `@TestInstance(Lifecycle.PER_CLASS)`, reuses **one** instance for all tests in the class — covered in its own question — which trades isolation for shared state and lets `@BeforeAll`/`@AfterAll` be non-static.

  • If two tests each increment the same instance field, why don't they interfere in PER_METHOD mode?
    Because each test runs on a separate instance of the test class, so each gets its own copy of that field starting from the initial value — there is no shared object for them to clash over.
  • Why must @BeforeAll be static under PER_METHOD?
    There is no single shared instance to invoke it on (a new one is made per test), so JUnit calls it at the class level, which requires a static method.

Like getting a fresh, clean exam booklet for every question instead of reusing one scribbled-on sheet — nothing from the last answer bleeds into the next.

saying these in an interview costs you the question

  • Claiming JUnit reuses one instance by default (that's PER_CLASS, the non-default mode)
  • Saying instance field values persist from one test to the next in default mode
  • Confusing @BeforeEach (per test) with @BeforeAll (once per class)
  • Thinking you must make tests static or use shared state to share setup

context