Custom extensions let you add your own rules or actions to JUnit tests. This helps you run extra code before, after, or around tests easily.
Custom extensions in JUnit
public class MyExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
// code to run before each test
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
// code to run after each test
}
}Implement interfaces like BeforeEachCallback or AfterEachCallback to hook into test lifecycle.
Use ExtensionContext to get info about the test or store data.
public class TimingExtension implements BeforeEachCallback, AfterEachCallback { private long startTime; @Override public void beforeEach(ExtensionContext context) { startTime = System.currentTimeMillis(); } @Override public void afterEach(ExtensionContext context) { long duration = System.currentTimeMillis() - startTime; System.out.println("Test took " + duration + " ms"); } }
@ExtendWith to apply your custom extension to a test class.@ExtendWith(TimingExtension.class) public class MyTests { @Test void testExample() { // test code } }
public class RetryExtension implements TestExecutionExceptionHandler { private int maxRetries = 3; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { int attempt = 0; while (attempt < maxRetries) { attempt++; System.out.println("Retry " + attempt + " for test " + context.getDisplayName()); try { context.getRequiredTestMethod().invoke(context.getRequiredTestInstance()); return; } catch (Throwable ignored) { // continue to next retry } } throw throwable; } }
This test class uses the TimingExtension to print how long each test takes. The extension runs code before and after each test automatically.
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.*; public class TimingExtension implements BeforeEachCallback, AfterEachCallback { private long startTime; @Override public void beforeEach(ExtensionContext context) { startTime = System.currentTimeMillis(); } @Override public void afterEach(ExtensionContext context) { long duration = System.currentTimeMillis() - startTime; System.out.println("Test took " + duration + " ms"); } } @ExtendWith(TimingExtension.class) public class SampleTest { @Test void fastTest() throws InterruptedException { Thread.sleep(50); } @Test void slowTest() throws InterruptedException { Thread.sleep(150); } }
Always keep extension code simple and fast to avoid slowing tests.
Use ExtensionContext to share data between callbacks.
Register extensions with @ExtendWith or programmatically in test suites.
Custom extensions let you add reusable code around tests.
Implement JUnit extension interfaces to hook into test lifecycle.
Apply extensions with @ExtendWith to run extra code automatically.
Practice
@ExtendWith?Solution
Step 1: Understand the role of custom extensions
Custom extensions allow adding reusable code that hooks into the test lifecycle, such as setup or teardown actions.Step 2: Recognize the use of @ExtendWith
The annotation@ExtendWithapplies these extensions to tests so the extra code runs automatically before or after tests.Final Answer:
To add reusable code that runs before or after tests automatically -> Option DQuick Check:
Custom extensions = reusable lifecycle code [OK]
- Thinking extensions skip assertions
- Confusing extensions with Java version settings
- Assuming extensions generate reports
MyExtension to a JUnit 5 test class?Solution
Step 1: Recall the correct annotation for extensions
JUnit 5 uses@ExtendWithto apply extensions to test classes or methods.Step 2: Check the syntax for specifying the extension class
The extension class must be passed as a class literal with.class, so@ExtendWith(MyExtension.class)is correct.Final Answer:
@ExtendWith(MyExtension.class) -> Option AQuick Check:
@ExtendWith + .class = correct syntax [OK]
- Omitting .class after the extension name
- Using wrong annotation names like @UseExtension
- Passing extension name without parentheses
public class PrintExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
System.out.println("Before test: " + context.getDisplayName());
}
}
@ExtendWith(PrintExtension.class)
public class SampleTest {
@Test
void testOne() {}
}Solution
Step 1: Understand BeforeEachCallback behavior
ThebeforeEachmethod runs before each test method and prints the test's display name.Step 2: Identify the display name for the test method
The display name is the test method name with parentheses, so it prints "Before test: testOne()".Final Answer:
Before test: testOne() -> Option AQuick Check:
beforeEach prints test method name [OK]
- Thinking it prints class name instead of method
- Assuming no output is printed
- Confusing interface implementation
public class FaultyExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
int x = 1 / 0;
}
}
@ExtendWith(FaultyExtension.class)
public class TestClass {
@Test
void testMethod() {}
}What will happen when running
testMethod?Solution
Step 1: Analyze the extension code for errors
The extension'sbeforeEachmethod divides by zero, causing an ArithmeticException at runtime.Step 2: Understand effect on test execution
Since the exception occurs before the test runs, the test fails immediately with that exception.Final Answer:
The test will fail with an ArithmeticException before running -> Option BQuick Check:
Runtime error in extension causes test failure [OK]
- Thinking division by zero causes compile error
- Assuming test passes despite extension error
- Believing test is skipped silently
Solution
Step 1: Identify lifecycle timing for setup/teardown
Setting up before all tests and closing after all tests requires hooks that run once per test class, not per test method.Step 2: Match interfaces to lifecycle events
BeforeAllCallbackruns before all tests in a class, andAfterAllCallbackruns after all tests in a class, perfect for this use case.Final Answer:
BeforeAllCallback and AfterAllCallback -> Option CQuick Check:
BeforeAll + AfterAll = class-level setup/teardown [OK]
- Using BeforeEachCallback for class-level setup
- Confusing TestInstancePostProcessor with lifecycle callbacks
- Choosing test execution callbacks instead of all callbacks
Ready to prove it?
Pass the test and this pattern moves unknown
