What if you could stop wasting time creating new test objects for every single test method?
Why TestInstance lifecycle per class in JUnit? - Purpose & Use Cases
Imagine running multiple tests on a class where each test needs to create a new object every time. You have to manually create and clean up these objects before and after each test.
This manual approach is slow and repetitive. You might forget to reset the object state, causing tests to interfere with each other. It's easy to make mistakes and waste time rewriting setup code.
Using the TestInstance lifecycle per class in JUnit lets you create one test object for all tests in a class. This means setup happens once, tests share the same instance, and you avoid repeated object creation and cleanup.
class TestExample {
MyClass obj;
@BeforeEach
void setup() {
obj = new MyClass();
}
@Test
void test1() { ... }
@Test
void test2() { ... }
}@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestExample {
MyClass obj = new MyClass();
@Test
void test1() { ... }
@Test
void test2() { ... }
}You can write cleaner, faster tests by sharing one test instance across all tests in a class, reducing setup time and avoiding repeated code.
When testing a database connection class, you open the connection once per test class instead of opening and closing it before every test method, saving time and resources.
Manual setup for each test is slow and error-prone.
TestInstance lifecycle per class creates one shared test object.
This reduces repeated setup and speeds up testing.
Practice
@TestInstance(TestInstance.Lifecycle.PER_CLASS) annotation do in a JUnit test class?Solution
Step 1: Understand the annotation purpose
The@TestInstance(TestInstance.Lifecycle.PER_CLASS)annotation tells JUnit to create only one instance of the test class for all test methods.Step 2: Compare with default behavior
By default, JUnit creates a new instance before each test method, but PER_CLASS changes this to reuse the same instance.Final Answer:
Creates a single test instance shared by all test methods in the class. -> Option AQuick Check:
PER_CLASS means one instance per class [OK]
- Confusing PER_CLASS with default PER_METHOD lifecycle
- Thinking PER_CLASS disables instance creation
- Assuming PER_CLASS runs tests in parallel
Solution
Step 1: Recall the full annotation syntax
The correct syntax uses the full enum path:@TestInstance(TestInstance.Lifecycle.PER_CLASS).Step 2: Check other options
@TestInstance(Lifecycle.PER_CLASS) misses theTestInstance.prefix, @TestInstance(PER_CLASS) is incomplete, and @TestInstance(Lifecycle.PER_METHOD) uses PER_METHOD which is default, not PER_CLASS.Final Answer:
@TestInstance(TestInstance.Lifecycle.PER_CLASS) -> Option AQuick Check:
Full enum path required in annotation [OK]
- Omitting TestInstance prefix in annotation
- Using PER_METHOD instead of PER_CLASS
- Using incorrect enum names or syntax
@TestInstance(TestInstance.Lifecycle.PER_CLASS):
import org.junit.jupiter.api.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CounterTest {
private int count = 0;
@Test
void testIncrement() {
count++;
Assertions.assertEquals(1, count);
}
@Test
void testIncrementAgain() {
count++;
Assertions.assertEquals(2, count);
}
}
What will be the result when running these tests?Solution
Step 1: Understand PER_CLASS effect on instance
With PER_CLASS, the same test instance is used for both tests, so the fieldcountkeeps its value between tests.Step 2: Trace the count value changes
First test increments count from 0 to 1 and asserts 1. Second test increments count from 1 to 2 and asserts 2. Both assertions are correct.Final Answer:
Both tests pass because count is shared and increments correctly. -> Option DQuick Check:
Shared instance means shared state [OK]
- Assuming count resets before each test
- Expecting independent test instances
- Confusing PER_CLASS with PER_METHOD lifecycle
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SampleTest {
private List<String> list = new ArrayList<>();
@BeforeEach
void setup() {
list.clear();
list.add("start");
}
@Test
void testOne() {
list.add("one");
Assertions.assertEquals(2, list.size());
}
@Test
void testTwo() {
list.add("two");
Assertions.assertEquals(2, list.size());
}
}
If the list.clear() call is removed from setup(), what problem will occur?Solution
Step 1: Analyze list state without clearing
Withoutlist.clear(), the list keeps accumulating elements from previous tests because the same instance is reused.Step 2: Understand assertion impact
Each test expects list size 2, but after first test, list size grows beyond 2, causing assertions to fail in subsequent tests.Final Answer:
Tests will fail because list keeps growing between tests. -> Option BQuick Check:
Shared state must be reset to avoid test interference [OK]
- Forgetting to reset mutable fields in PER_CLASS tests
- Assuming new instance per test with PER_CLASS
- Ignoring side effects between tests
@TestInstance(TestInstance.Lifecycle.PER_CLASS). Which of the following is the best practice to avoid flaky tests?Solution
Step 1: Understand shared state risks
With PER_CLASS lifecycle, the same instance is reused, so mutable fields keep their values across tests, risking interference.Step 2: Apply best practice for test isolation
Resetting mutable shared state in@BeforeEachensures each test starts fresh, preventing flaky or dependent tests.Final Answer:
Reset all mutable shared state in a@BeforeEachmethod. -> Option CQuick Check:
Reset shared state before each test to avoid flaky tests [OK]
- Relying only on constructor for setup with PER_CLASS
- Using static fields causing global state issues
- Ignoring test isolation principles
Ready to prove it?
Pass the test and this pattern moves unknown
