JUnit
The default unit-testing framework on the JVM: annotations, assertions, lifecycle, parameterized tests, and the Jupiter extension model. Interviewers ask because nearly every Java or Kotlin codebase runs on it, and your habits here show up in every test you write.
on this pageshowhide
explore
- Test Annotations22 questions
- @Test & Display Names4 questions
- @Disabled5 questions
- @Timeout5 questions
- @RepeatedTest4 questions
- Composed Annotations4 questions
- Assertions & Assumptions27 questions
- Equality & Array Assertions5 questions
- assertAll Grouping4 questions
- Exception Assertions4 questions
- Timeout Assertions5 questions
- Failure Messages4 questions
- Assumptions5 questions
- Lifecycle & Fixtures18 questions
- Setup & Teardown Hooks4 questions
- Test Instance Lifecycle5 questions
- Test Ordering5 questions
- Lifecycle in @Nested Classes4 questions
- Parameterized Tests27 questions
- @ValueSource & @EnumSource4 questions
- @CsvSource & @CsvFileSource5 questions
- @MethodSource & @ArgumentsSource5 questions
- Argument Conversion5 questions
- Argument Aggregation5 questions
- Invocation Display Names3 questions
- Extension Model28 questions
- Extension Callbacks6 questions
- Registering Extensions4 questions
- ExtensionContext & Store4 questions
- ParameterResolver4 questions
- Conditions & Watchers5 questions
- @TempDir5 questions
- Test Runners & Suites20 questions
- @RunWith & JUnit 4 Runners5 questions
- JUnit 4 Rules6 questions
- Vintage Engine4 questions
- JUnit 4 to 5 Migration5 questions
- Nested & Dynamic Tests15 questions
- @Nested Organization5 questions
- @TestFactory5 questions
- DynamicTest & DynamicContainer5 questions
- Tags, Conditions & Parallelism19 questions
- JUnit Platform15 questions
- Platform / Jupiter / Vintage Split3 questions
- TestEngine & Launcher API5 questions
- Build-Tool Wiring2 questions
- @Suite on the Platform5 questions
questions
191 · 9 sectionsIn JUnit 5, some teams mark tests with one custom annotation such as @SlowIntegrationTest instead of writing @Test plus @Tag("slow") on every method. What is that custom annotation called, and how does JUnit know to treat the method as a test?
basics
~20 sIt is a composed annotation: your own annotation type that is itself annotated with JUnit's @Test, @Tag, @ExtendWith and so on. Jupiter looks up annotations recursively, so anything meta-annotated with @Test is discovered and run as a test.
In JUnit 5, what does the @Disabled annotation do, how does putting it on a test class differ from putting it on a single test method, and what is the string argument used for?
basics
~20 s@Disabled skips a test instead of running it, reporting it as skipped rather than failed. On a method it skips that method; on a class it skips every test in the class, including its nested classes. The string argument is the reason, shown in reports and IDEs.
What rules must a Java method (and its enclosing class) satisfy for the JUnit 5 Jupiter engine to discover and run it as a test?
basics
~20 sAnnotate it with org.junit.jupiter.api.Test. The method must not be private, static or abstract, and must return void. Parameters are allowed only if a ParameterResolver supplies them (TestInfo, TestReporter, @TempDir...). The class must not be abstract and needs a single constructor; neither class nor method has to be public.
In JUnit 5, how do you make a single test fail automatically if it runs longer than a chosen duration, and what exactly happens when that limit is reached?
basics
~20 sAnnotate the test with JUnit Jupiter's @Timeout, e.g. @Timeout(value = 500, unit = TimeUnit.MILLISECONDS). The default unit is seconds. When the limit elapses the test fails with a TimeoutException naming the method and the configured duration.
You wrote a custom JUnit 5 annotation that bundles @Test and @Tag, but methods marked with it are no longer picked up as tests. Which Java meta-annotations must your annotation type itself declare, and what goes wrong when each is missing or wrong?
basics
~20 s@Retention(RetentionPolicy.RUNTIME) is mandatory — the default CLASS retention hides the annotation from reflection, so JUnit never sees it. @Target must include the element you annotate (METHOD for a test marker, TYPE for class-level, ANNOTATION_TYPE to allow further composition).
In JUnit 5, how do you make a test stop at runtime and report itself as skipped when a precondition is not satisfied, and how does that outcome differ from a failure?
basics
~20 sCall Assumptions.assumeTrue(condition) (or assumeFalse) at the top of the test. If the condition does not hold, a TestAbortedException is thrown, the rest of the method never runs, and the test is reported as aborted or skipped — the build stays green rather than red.
In JUnit 5, what is the difference between assertEquals and assertSame, and when would you deliberately reach for assertSame?
basics
~20 sassertEquals compares with equals() — value equality. assertSame compares with == — the same object in memory. Use assertSame only when identity is the contract you are testing: caches, interned or flyweight instances, singletons, or a method that returns this.
How do you assert in JUnit 5 that a piece of code throws a particular exception, and how do you then verify details of that exception such as its message or cause?
basics
~20 sCall assertThrows(ExpectedType.class, () -> codeUnderTest()). It fails if nothing is thrown or if the thrown type does not match, and on success it returns the thrown exception, typed, so you can go on asserting its message, cause or custom fields.
A JUnit 5 test checks five properties of one returned object; the first failing check ends the test and the other four never run, so each fix only reveals the next problem. What does org.junit.jupiter.api.Assertions.assertAll give you here, and how do you use it?
basics
~20 sassertAll takes lambdas, runs every one of them even after some fail, then reports all failures together in one aggregated error. Use it for independent checks on the same object so a single run shows every problem instead of only the first.
In JUnit 5 (Jupiter), how do you assert inside a test that a block of code finishes within a given time budget, and what does the framework report when the block is too slow?
basics
~20 sUse Assertions.assertTimeout(Duration.ofMillis(500), () -> code). It runs the block on the test thread, then fails if it took longer, reporting the expected budget and how much it overran. A supplier form returns the block's value.
In JUnit 5, describe when the methods annotated @BeforeAll, @BeforeEach, @AfterEach and @AfterAll run relative to the test methods of a class, and what each is typically used for.
basics
~20 s@BeforeAll runs once before any test in the class; @AfterAll runs once after the last one. @BeforeEach runs before every individual test and @AfterEach after every one, so for three tests you get one BeforeAll, three BeforeEach/test/AfterEach cycles, then one AfterAll.
In JUnit 5 (Jupiter), if a test class has three @Test methods, how many instances of that class does the framework create by default, and what is the reason for that design?
basics
~20 sThree. Jupiter's default lifecycle is PER_METHOD: a brand-new instance of the test class is constructed before each @Test method. Instance fields therefore start fresh every time, so one test cannot leak state into another through fields.
In JUnit 5 (Jupiter), what order are the @Test methods inside one test class executed in when you do not configure anything, and what does that imply for how those tests must be written?
basics
~20 sJupiter applies no orderer by default, so methods run in a deterministic but intentionally non-obvious order — not source order, not alphabetical — and it may change between versions. Each test must therefore pass independently, in any order.
Why does JUnit 5 normally require @BeforeAll and @AfterAll methods to be declared static, and what happens if you leave the keyword off?
basics
~20 sBy default JUnit creates a new test-class instance for every test method, so there is no single instance a class-level hook could belong to; it must be static to be callable once per class. Without static you get a runtime JUnitException telling you the method must be static, not a compile error.
What changes in a JUnit 5 test class when you annotate it with @TestInstance(TestInstance.Lifecycle.PER_CLASS), and why would you want that?
basics
~20 sJupiter creates one instance of the class for all its test methods instead of one per method. Instance fields then survive between tests, and @BeforeAll/@AfterAll (and non-static @MethodSource factory methods) may be non-static because there is now an instance to own them.
In JUnit 5, how does the @CsvSource annotation supply arguments to a parameterized test method, and how does each row map to that method's parameters?
basics
~20 s@CsvSource sits next to @ParameterizedTest and holds an array of comma-separated row strings. JUnit runs the test method once per row, splitting the row on commas and passing the pieces, left to right, as the method's parameters.
In JUnit 5, how does the @MethodSource annotation supply arguments to a parameterized test, and what happens if you leave the annotation's value empty?
basics
~20 s@MethodSource names a factory method that returns a Stream (or Iterable/array) of arguments; JUnit runs the test once per element. If you leave the value empty, JUnit looks for a factory method with the same name as the test method.
In JUnit 5, a data-driven test method runs once per set of arguments and each run appears as its own entry in the IDE tree and the XML report. How do you control the text shown for each of those per-run entries, and what does that text look like if you do nothing?
basics
~10 sSet the name attribute of @ParameterizedTest, e.g. @ParameterizedTest(name = "[{index}] input={0}"). JUnit fills placeholders per invocation: {index} (1-based), {arguments}, {argumentsWithNames}, {displayName}, and positional {0}, {1}. Default pattern is "[{index}] {argumentsWithNames}".
You want a JUnit 5 test method to run once for each of the literal inputs 1, 2 and 3 without writing a loop inside the test. Which annotations do you use, and what are the limitations of that argument source?
basics
~20 sMark the method @ParameterizedTest and add @ValueSource(ints = {1, 2, 3}); it runs once per value, passing one argument each time. Limits: exactly one array attribute, only primitives plus String and Class, values must be compile-time constants, and null cannot be supplied.
A JUnit 5 parameterized test is fed rows of twelve columns and you don't want a twelve-parameter test method. What does declaring a parameter of type ArgumentsAccessor give you, and how do you read values out of it?
basics
~20 sDeclare one parameter of type ArgumentsAccessor. JUnit passes it the whole argument array for that invocation instead of consuming a single column. You read values positionally: getString(0), getInteger(3), get(5, LocalDate.class), plus size(), toList() and toArray().
In JUnit 5 (Jupiter), how do you plug a reusable extension such as the Mockito or Spring test integration into a test, and which declaration sites are valid for the @ExtendWith annotation?
basics
~20 sDeclaratively, with @ExtendWith(SomeExtension.class). It can go on a test class, on a single test method, on a field or parameter, or on your own meta-annotation. JUnit builds the extension with its no-arg constructor, and class-level registration is inherited by subclasses and @Nested classes.
In JUnit 5, how do you obtain a throwaway directory for a test that needs to read and write real files, and what field or parameter types can receive it?
basics
~20 sAnnotate a field or a method parameter of type java.nio.file.Path or java.io.File with JUnit Jupiter's @TempDir. Jupiter's built-in extension creates a fresh empty directory in the OS temp location before use and recursively deletes it, with its contents, afterwards.
JUnit 5's extension model defines BeforeAllCallback, BeforeEachCallback, BeforeTestExecutionCallback and their After* counterparts. In what order do these run around a single test method, and how does that order change when several extensions are registered?
basics
~10 sOrder is BeforeAllCallback, @BeforeAll, BeforeEachCallback, @BeforeEach, BeforeTestExecutionCallback, the test, AfterTestExecutionCallback, @AfterEach, AfterEachCallback, @AfterAll, AfterAllCallback. Multiple extensions nest like an onion: Before* in registration order, After* in reverse.
In JUnit 5, how do you make a test class or a test method skip itself automatically based on a runtime check — for example, a required database not being reachable — using the extension model rather than commenting the test out?
basics
~20 sImplement JUnit 5's ExecutionCondition extension. Its evaluateExecutionCondition(ExtensionContext) returns ConditionEvaluationResult.enabled(reason) or disabled(reason). JUnit calls it for every container and test it is registered on; a disabled result skips that node, reported as skipped rather than failed.
How do you make JUnit 5 hand your test methods a ready-made collaborator — say a pre-configured HTTP client — as a method argument? Walk through the extension interface, its two methods, and how you keep it from claiming parameters it should not.
basics
~20 sImplement ParameterResolver: supportsParameter(ParameterContext, ExtensionContext) returns true only for parameters you own, and resolveParameter(...) returns the object. Register it with @ExtendWith. Narrow supportsParameter by both a custom annotation and the parameter type so it never competes with another resolver.
When moving a test class from JUnit 4 to JUnit 5, what are the JUnit 5 equivalents of @Before, @BeforeClass, @After, @AfterClass, @Ignore and @Category, and what changes besides the names?
basics
~10 s@Before becomes @BeforeEach, @BeforeClass becomes @BeforeAll, @After becomes @AfterEach, @AfterClass becomes @AfterAll, @Ignore becomes @Disabled, @Category(X.class) becomes @Tag("x"). Packages change to org.junit.jupiter.api, assertions move to Assertions, and the message argument moves to last.
In JUnit 4, what is a `@Rule`, and what can it do that plain `@Before` and `@After` methods cannot?
basics
~20 sA @Rule is a public, non-static field holding an object that wraps each test method. Because it wraps the call, it can run code before and after the test and also catch failures, retry, time out or skip it. @Before/@After only run beside the test.
What does JUnit 4's `@RunWith` annotation do, and what executes a test class that carries no `@RunWith` at all?
basics
~20 s@RunWith(X.class) names the Runner class that takes over running that test class. Without it, JUnit falls back to its default runner — org.junit.runners.JUnit4, a subclass of BlockJUnit4ClassRunner — which finds the @Test methods, creates a new instance per method and applies @Before/@After and rules.
The JUnit Platform can execute legacy JUnit 4 tests through an engine named junit-vintage-engine. Explain what that engine actually is, what kinds of test classes it accepts, and what it does NOT change about how those tests behave.
basics
~20 sjunit-vintage-engine is a TestEngine implementation for the JUnit Platform. It discovers JUnit 3 (junit.framework.TestCase) and JUnit 4 classes, delegates execution to JUnit 4's own runners, and reports results back to the platform. The tests keep pure JUnit 4 semantics and gain no Jupiter features.
JUnit 4 offered @Test(expected = SomeException.class) and the ExpectedException rule for exception tests. How do you express the same intent in JUnit 5, and why is the replacement considered stronger?
basics
~20 sUse Assertions.assertThrows(SomeException.class, () -> code()), which returns the thrown exception so you can assert its message or cause. It scopes the expectation to one statement, unlike @Test(expected=), which passes if any line in the method throws that type.
In JUnit 5, what is a DynamicTest object, how do you construct one, and how does it differ at runtime from a plain method annotated with @Test?
basics
~20 sA DynamicTest is a test case created at runtime as an object, via DynamicTest.dynamicTest(displayName, executable) — a display-name string plus a lambda holding the assertions. Unlike a @Test method it is not discovered by annotation scanning and carries no annotations of its own.
In JUnit 5, what does annotating an inner test class with @Nested do, and why must that class be non-static?
basics
~20 s@Nested makes an inner class a child test container: its tests run under the outer class and reuse the outer class's fields and @BeforeEach setup. It must be non-static so every nested instance is bound to a live enclosing instance.
A developer reports that in JUnit 5 their @BeforeEach method runs only once even though the factory method they wrote produced twenty executable test cases at runtime. Why does that happen, and how do you give each generated case its own fresh setup?
basics
~20 sDynamic tests are generated at runtime, so Jupiter's per-test callbacks bind to the generating method, not to each generated case: @BeforeEach and @AfterEach run once around the whole factory. Put setup and teardown inside each executable, typically via a shared wrapper helper.
For a test method declared inside a JUnit 5 @Nested class, in what order do the enclosing and nested lifecycle callbacks run, how many objects does the framework create, and what does it take to declare @BeforeAll inside that nested class?
basics
~20 s@BeforeEach runs outside-in (outer then nested), @AfterEach inside-out. With the default per-method lifecycle each test gets a fresh outer instance plus a fresh nested instance. @BeforeAll must be static, which needs Java 16+ or @TestInstance(PER_CLASS) on the nested class.
What is a @TestFactory method in JUnit 5, and how does its execution differ from a plain @Test method?
basics
~20 sA @TestFactory method is not a test; it is a factory invoked at runtime that returns dynamic test nodes, which JUnit then executes. Cases are produced during execution rather than fixed at discovery, so the number and names of tests can depend on runtime data.
JUnit 5 is usually described as three sub-projects rather than one library. What are they, and what is each one responsible for when tests actually run?
basics
~20 sJUnit 5 = Platform + Jupiter + Vintage. The Platform is the foundation: it launches tests and defines the TestEngine plug-in interface. Jupiter is the new programming model (annotations, assertions) plus its own engine. Vintage is an engine that runs old JUnit 3 and 4 tests.
You need to discover and run JUnit tests from your own Java code — for example inside a custom tool that picks which tests to run — rather than letting a build tool do it. Which JUnit Platform API do you use, and what are the steps?
basics
~20 sUse the JUnit Platform Launcher API from junit-platform-launcher: build a LauncherDiscoveryRequest with selectors and filters, open a LauncherSession to get a Launcher, register a TestExecutionListener, then call discover() to inspect the TestPlan or execute() to run it.
In JUnit 5, how do you declare a test suite that runs a chosen set of test classes, and which annotations decide what goes into it?
basics
~10 sPut @Suite on a plain class, then add selectors: @SelectClasses lists test classes explicitly, @SelectPackages scans whole packages. The junit-platform-suite-engine artifact must be on the test runtime classpath, otherwise the suite class runs nothing.
In a JUnit 5 project, junit-jupiter-api is normally a compile-scope test dependency while junit-jupiter-engine is runtime-only. Why is the API split from the engine at all, and what does the aggregate junit-jupiter artifact contain?
basics
~20 sTest code compiles only against the API (annotations, assertions, extensions); the engine is an implementation found at runtime through ServiceLoader. Keeping the engine off the compile path stops code depending on engine internals. The aggregate junit-jupiter artifact bundles api plus params, with the engine at runtime.
JUnit's platform reads settings such as parallel test execution and the default test-instance lifecycle from configuration parameters. Where can those parameters be supplied, and which source wins when two of them disagree?
basics
~20 sConfiguration parameters are simple key/value settings. They can come from the LauncherDiscoveryRequest, a JVM system property, or a junit-platform.properties file at the root of the classpath — and that is also the precedence order, request first, file last.