skip to content

JUnit (Unit Testing)

Writing tests with JUnit 5: the annotations, lifecycle callbacks, assertions, parameterized tests, nesting, and the test instance lifecycle. Interviewers use it as the baseline check that you actually write tests rather than talk about them.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

64 · 13 sections

What are the main annotation differences between JUnit 4 and JUnit 5, and how would you migrate a simple test class?

level: juniorimportance: must knowfreq 70%
basics
~10 s

JUnit 5 renames the lifecycle annotations: @Before becomes @BeforeEach, @After becomes @AfterEach, @BeforeClass becomes @BeforeAll, @AfterClass becomes @AfterEach's partner @AfterAll, and @Ignore becomes @Disabled. They also move to the org.junit.jupiter.api package.

open as a page

Describe the three-part architecture of JUnit 5 (Platform, Jupiter, Vintage) and why it was split that way.

level: middleimportance: must knowfreq 55%
basics
~20 s

JUnit 5 is three pieces: the Platform (launches tests and lets build tools/IDEs run them), Jupiter (the new way to write and run tests), and Vintage (an engine that runs your old JUnit 4 tests). JUnit 4 was a single library with no such separation.

open as a page

How do JUnit 5 assertions and exception testing differ from JUnit 4's, including the assertion message and expected-exception handling?

level: middleimportance: should knowfreq 50%
basics
~20 s

JUnit 5 assertions live in org.junit.jupiter.api.Assertions, the optional message moved to the last argument (it was first in JUnit 4), and you test exceptions with assertThrows(...) instead of @Test(expected=...). JUnit 5 also adds assertAll and assertThrows returning the caught exception.

open as a page

How does JUnit 5's extension model (@ExtendWith) differ from JUnit 4's @RunWith and Rules, and why is it considered an improvement?

level: seniorimportance: should knowfreq 45%
basics
~20 s

JUnit 4 customized test behavior with @RunWith (only one runner per class) and @Rule objects. JUnit 5 replaces both with extensions registered via @ExtendWith, and you can apply as many as you want, composing them freely.

open as a page

You inherit a large codebase with thousands of JUnit 4 tests. How do you adopt JUnit 5 incrementally, and what are the trade-offs of the Vintage bridge?

level: principalimportance: should knowfreq 35%
basics
~20 s

Add the JUnit 5 dependencies including the Vintage engine, so old JUnit 4 tests keep running while you write all new tests in JUnit 5. Then migrate old tests gradually, removing Vintage once they're all converted.

open as a page

What does the @Test annotation do in JUnit 5, and what are the signature requirements for a method marked with it?

level: juniorimportance: must knowfreq 85%
basics
~10 s

@Test marks a method as a test case so JUnit runs it. The method must return void and take no parameters (unless something is injected), and should not be static or private.

open as a page

JUnit 4 let you write @Test(expected = SomeException.class) and @Test(timeout = 1000). How do you express those in JUnit 5, and why was the change made?

level: middleimportance: must knowfreq 70%
basics
~10 s

JUnit 5's @Test has no expected or timeout attributes. Instead you use assertThrows to check an exception is thrown and assertTimeout (or @Timeout) to bound how long code may run.

open as a page

A developer writes a method marked @Test but it never runs — no pass, no fail, no error. What are the common reasons a @Test method is silently not discovered or executed by JUnit 5?

level: middleimportance: should knowfreq 55%
basics
~20 s

Usual causes: importing JUnit 4's org.junit.Test instead of org.junit.jupiter.api.Test, the method being private or static, the test class or method matching no naming/include pattern in the build config, or the JUnit Platform/Jupiter engine not being on the runtime classpath.

open as a page

How does the JUnit 5 Jupiter engine discover and execute methods annotated with @Test, and where does @Test fit within the JUnit 5 architecture?

level: seniorimportance: should knowfreq 45%
basics
~20 s

JUnit 5 has a Platform that launches tests, a Jupiter engine that finds @Test methods by scanning classes via reflection, and the API (@Test) you annotate with. The engine builds a test plan, creates an instance per test, runs lifecycle callbacks, then invokes each @Test method.

open as a page

Why does JUnit 5's @Test have no attributes at all, and how does that decision interact with composed (meta-)annotations and the extension model?

level: principalimportance: nice to knowfreq 25%
basics
~20 s

JUnit 5 keeps @Test a bare marker and moves behavior (exceptions, timeouts, conditions) into separate assertions, annotations, and extensions. That makes @Test composable: you can build your own annotations that combine @Test with tags or extensions, and add cross-cutting behavior without changing @Test itself.

open as a page

What do @BeforeEach and @AfterEach do in JUnit 5, and why would you use them?

level: juniorimportance: must knowfreq 70%
basics
~20 s

@BeforeEach runs before every test method and @AfterEach runs after each one. You use them to set up a fresh starting state (like creating objects) before each test and to clean up afterward, so tests don't affect each other.

open as a page

What is the difference between @BeforeEach/@AfterEach and @BeforeAll/@AfterAll in JUnit 5?

level: middleimportance: must knowfreq 65%
basics
~20 s

@BeforeEach/@AfterEach run before and after every single test. @BeforeAll/@AfterAll run only once for the whole test class — before any test and after all tests. The 'Each' ones are for per-test setup; the 'All' ones are for expensive shared setup.

open as a page

When a JUnit 5 test class extends a base test class, in what order do the @BeforeEach and @AfterEach methods of the superclass and subclass execute?

level: seniorimportance: should knowfreq 40%
basics
~10 s

Superclass @BeforeEach methods run before subclass @BeforeEach methods, and subclass @AfterEach methods run before superclass @AfterEach methods. It nests like constructors: setup goes top-down (parent first), cleanup goes bottom-up (child first).

open as a page

Why is @BeforeEach the standard way to achieve test isolation, and what are the main pitfalls of relying on @BeforeAll for shared mutable state?

level: seniorimportance: should knowfreq 38%
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.

open as a page

In JUnit 5, when should you initialize test state in a @BeforeEach method versus in the test class's constructor or field initializers?

level: middleimportance: nice to knowfreq 30%
basics
~20 s

Because JUnit makes a new test-class instance for each test, the constructor and field initializers also run before every test, so simple setup works there too. Prefer @BeforeEach when setup needs framework features (like injected parameters) or should run as the documented lifecycle step; use the constructor for plain field assignment.

open as a page

What do @BeforeAll and @AfterAll do in JUnit 5, and how do they differ from @BeforeEach and @AfterEach?

level: juniorimportance: must knowfreq 70%
basics
~10 s

@BeforeAll runs once before all the tests in a class; @AfterAll runs once after all of them. @BeforeEach and @AfterEach run before and after every single test method instead.

open as a page

How do you decide whether a piece of setup belongs in @BeforeAll or @BeforeEach? What's the trade-off?

level: middleimportance: must knowfreq 60%
basics
~20 s

Put setup in @BeforeAll if it's expensive and the same for every test and safe to share (it runs once). Put it in @BeforeEach if each test needs its own fresh copy (it runs every time). The trade-off is speed (BeforeAll) versus isolation (BeforeEach).

open as a page

Why must @BeforeAll and @AfterAll methods be static under JUnit 5's default lifecycle?

level: middleimportance: must knowfreq 65%
basics
~20 s

By default JUnit 5 creates a new instance of the test class for every test method. @BeforeAll/@AfterAll run once, before any instance exists, so they can't belong to an instance — they must be static (belong to the class itself).

open as a page

How would you use @BeforeAll and @AfterAll to manage an expensive shared resource like a test container or embedded server, and what should you watch out for?

level: seniorimportance: should knowfreq 55%
basics
~20 s

Start the expensive resource (e.g. a database container) once in @BeforeAll and stop it once in @AfterAll, so all tests in the class reuse it instead of paying the startup cost each time. Make sure each test cleans up its own data so tests stay independent.

open as a page

When a test class inherits from a base class with its own @BeforeAll, and there are multiple @BeforeAll methods, in what order does JUnit 5 run them — and how does @AfterAll mirror that?

level: principalimportance: nice to knowfreq 30%
basics
~20 s

Inherited @BeforeAll methods from a superclass run before the subclass's @BeforeAll; @AfterAll runs in the reverse order (subclass first, then superclass). Multiple @BeforeAll methods in the same class run in an unspecified order unless you set one with @Order.

open as a page

In JUnit 5's assertEquals, what is the argument order, and why does getting it wrong matter?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The order is assertEquals(expected, actual). The first value is what you expect, the second is what your code produced. Swapping them still passes or fails correctly, but the failure message reads backwards and confuses you.

open as a page

How do assertArrayEquals and assertIterableEquals compare their inputs, and how does that differ from calling assertEquals on a List or array?

level: middleimportance: should knowfreq 45%
basics
~20 s

assertArrayEquals compares two arrays element by element (in order). assertIterableEquals walks two iterables (like Lists) and compares elements pairwise. assertEquals on raw arrays compares references, not contents, so it usually fails even when contents match.

open as a page

What is the difference between assertEquals and assertSame in JUnit 5, and when would you use each?

level: middleimportance: should knowfreq 55%
basics
~20 s

assertEquals checks that two objects are equal by value (using .equals()). assertSame checks they are the exact same object in memory (using ==). Use assertSame only when identity matters, like verifying a cache returns the same instance.

open as a page

How do you correctly assert equality of floating-point values in JUnit 5, and why is a plain assertEquals on doubles dangerous?

level: middleimportance: should knowfreq 50%
basics
~10 s

Use the overload assertEquals(expected, actual, delta), where delta is a small tolerance. Floating-point math has rounding errors, so 0.1 + 0.2 isn't exactly 0.3. The delta says 'close enough'.

open as a page

JUnit 5 assertions accept an optional failure message — what forms can it take, and why is the Supplier form preferred for expensive messages?

level: middleimportance: should knowfreq 40%
basics
~20 s

You can pass a String message as the last argument, shown only when the assertion fails. JUnit also accepts a Supplier<String> lambda; the lambda runs only on failure, so a costly message isn't built every time the test passes.

open as a page

What is JUnit 5's assertThrows and how do you use it to verify that code throws an expected exception?

level: juniorimportance: must knowfreq 78%
basics
~20 s

assertThrows runs a piece of code and checks it throws the exception type you expect. You pass the expected exception class and a lambda holding the code. The test passes only if that code throws that type (or a subtype).

open as a page

How do you assert on the message and cause of an exception captured by assertThrows, and what makes such assertions robust rather than brittle?

level: middleimportance: should knowfreq 42%
basics
~20 s

assertThrows returns the exception, so you store it in a variable and call getMessage() or getCause() on it, then assert with normal assertEquals/assertTrue. To stay robust, check a stable substring or the cause's type rather than the entire exact message text.

open as a page

What is the difference between assertThrows and assertThrowsExactly in JUnit 5?

level: middleimportance: should knowfreq 55%
basics
~20 s

assertThrows passes if the code throws the expected type or any subclass of it. assertThrowsExactly passes only if the thrown exception is exactly that class — a subclass fails. Use exactly when the precise type matters.

open as a page

Why did JUnit 5's assertThrows replace JUnit 4's @Test(expected=...) and the ExpectedException rule?

level: seniorimportance: should knowfreq 48%
basics
~20 s

@Test(expected=...) only checked the type and let any line in the test throw it, so it was imprecise and couldn't check the message easily. ExpectedException needed setup before the call. assertThrows scopes the check to exact lines and returns the exception to inspect.

open as a page

What are the common pitfalls when using assertThrows, including swallowed exceptions, multiple statements in the executable, and using it where exception testing isn't the goal?

level: seniorimportance: nice to knowfreq 33%
basics
~20 s

Don't put many lines in the lambda — an earlier line could throw the expected type and make the test pass for the wrong reason. Wrap only the call you expect to throw. And don't use assertThrows just to silence a checked exception in a test.

open as a page

What is JUnit 5's assertAll, and what problem does it solve compared to writing several separate assertions?

level: juniorimportance: must knowfreq 55%
basics
~20 s

assertAll runs a group of assertions together and reports every failure at once. With plain asserts, the test stops at the first failure, so you only see one problem. assertAll shows all of them in one run.

open as a page

Why must each assertion inside assertAll be wrapped in a lambda (Executable), and what goes wrong if you call the assertions directly?

level: middleimportance: should knowfreq 45%
basics
~20 s

Each assertion is a lambda so assertAll can decide when to run it and catch its failure. If you called the asserts directly, the first failing one would throw right away and the rest would never run — defeating the point.

open as a page

When should you prefer grouped assertions with assertAll over independent assert calls, and when is assertAll the wrong choice?

level: seniorimportance: should knowfreq 40%
basics
~20 s

Use assertAll when you check several independent properties of one thing and want to see every failure at once. Avoid it when a later check depends on an earlier one passing, because assertAll runs them all even after a failure.

open as a page

How does assertAll behave with nested assertAll groups, and what are the precise semantics of its single-vs-multiple-failure reporting?

level: principalimportance: nice to knowfreq 22%
basics
~20 s

You can nest an assertAll inside another. If only one check fails, assertAll rethrows that exact failure; if several fail, it throws one MultipleFailuresError bundling them. Nesting groups related checks and keeps the failure tree organized.

open as a page

What is @ParameterizedTest in JUnit 5, and how does it differ from a plain @Test?

level: juniorimportance: must knowfreq 70%
basics
~20 s

@ParameterizedTest runs the same test method many times, once per set of input values. A plain @Test runs once with no arguments. You pair it with a source annotation (like @ValueSource) that supplies the values.

open as a page

What is @MethodSource, when is it the right choice, and what are the rules for the factory method it points to?

level: middleimportance: must knowfreq 58%
basics
~20 s

@MethodSource names a method that returns the test data, usually a Stream of Arguments. Use it when the inputs are real objects or computed at runtime — things you can't write as plain literals in @ValueSource or @CsvSource.

open as a page

Compare @ValueSource and @CsvSource. When would you choose each, and how do they map to test method parameters?

level: middleimportance: must knowfreq 62%
basics
~10 s

@ValueSource gives a single column of values to a one-parameter method. @CsvSource gives comma-separated rows, so each row maps to several parameters. Use @ValueSource for one input; use @CsvSource when you need input-plus-expected pairs.

open as a page

How do @EnumSource and the @ParameterizedTest name template work, including filtering enum constants and customizing display names?

level: middleimportance: should knowfreq 40%
basics
~20 s

@EnumSource runs the test once per constant of an enum, injecting each constant. You can include or exclude specific names with its mode/names attributes. The name attribute on @ParameterizedTest sets each invocation's display label using placeholders like {index} and {0}.

open as a page

Explain implicit and explicit argument conversion in @ParameterizedTest, and when you would use @ConvertWith or aggregate arguments with @AggregateWith / ArgumentsAccessor.

level: seniorimportance: should knowfreq 30%
basics
~20 s

JUnit automatically converts string source values (like from @CsvSource) into the parameter's type — that's implicit conversion. When the built-in rules can't do it, you supply a converter with @ConvertWith. To bundle many columns into one object, you use an ArgumentsAccessor or a custom aggregator via @AggregateWith.

open as a page

What is the @DisplayName annotation in JUnit 5 and why would you use it?

level: juniorimportance: must knowfreq 70%
basics
~20 s

@DisplayName is a JUnit 5 annotation that gives a test method or class a custom, human-readable name shown in reports and IDEs instead of the method name. It can contain spaces, punctuation, and even emojis.

open as a page

What is a DisplayNameGenerator and how do you apply one to derive readable test names automatically?

level: middleimportance: should knowfreq 48%
basics
~10 s

A DisplayNameGenerator is a JUnit 5 strategy that builds a test's display name from its class/method automatically. You attach one with @DisplayNameGeneration so you do not have to write @DisplayName on every test.

open as a page

For parameterized and repeated tests, how do per-invocation names relate to @DisplayName, and what controls them?

level: middleimportance: nice to knowfreq 34%
basics
~10 s

@DisplayName names the whole parameterized or repeated test. Each individual invocation gets its own name from the test's name pattern (the name attribute), not from @DisplayName.

open as a page

How do @Nested classes combine with @DisplayName and the IndicativeSentences generator to produce readable, hierarchical test reports?

level: seniorimportance: nice to knowfreq 30%
basics
~10 s

@Nested groups related tests into inner classes, each able to carry its own @DisplayName, so reports read as a tree. IndicativeSentences joins the enclosing names with the method name into one readable sentence.

open as a page

When standardizing test naming across a large codebase, how would you decide between explicit @DisplayName, a DisplayNameGenerator default, and method-name conventions?

level: principalimportance: nice to knowfreq 18%
basics
~20 s

Pick one default strategy for the whole codebase (often a generator like ReplaceUnderscores set via the config property), allow explicit @DisplayName to override where wording matters, and enforce it so reports stay consistent and low-maintenance.

open as a page

What does the @Disabled annotation do in JUnit 5, and what is the recommended way to use it?

level: juniorimportance: must knowfreq 70%
basics
~20 s

@Disabled tells JUnit to skip a test method or whole test class instead of running it. You should always pass a reason string, like @Disabled("flaky on CI - see TICKET-123"), so it is clear why it is off.

open as a page

Why does @Disabled take an optional reason string, and what is the cost of omitting it?

level: juniorimportance: should knowfreq 45%
basics
~10 s

The reason string explains why the test is turned off. JUnit prints it in the report. Without it, nobody knows why the test is disabled or when it is safe to turn back on.

open as a page

Walk through JUnit 5's conditional execution annotations - @EnabledOnOs, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, @EnabledIf - and how each decides whether a test runs.

level: middleimportance: should knowfreq 48%
basics
~20 s

These annotations turn a test on or off based on a condition: the operating system (@EnabledOnOs), a JVM system property (@EnabledIfSystemProperty), an OS environment variable (@EnabledIfEnvironmentVariable), or a custom boolean method (@EnabledIf). Each has a @Disabled... twin that does the opposite.

open as a page

When should you use a conditional annotation like @DisabledOnOs or @EnabledOnOs instead of an unconditional @Disabled?

level: middleimportance: should knowfreq 55%
basics
~20 s

Use @Disabled when a test is just broken or parked. Use conditional annotations like @EnabledOnOs(WINDOWS) or @DisabledOnOs(MAC) when the test should run only under a real condition - a specific OS, Java version, or environment - so it re-enables itself automatically when that condition holds.

open as a page

How does a disabled test interact with lifecycle callbacks, fixtures, and test reporting, and what risks come with accumulating disabled tests at scale?

level: seniorimportance: nice to knowfreq 30%
basics
~20 s

A disabled test is skipped entirely - its body and its @BeforeEach/@AfterEach for that test do not run, though class-level setup may still happen. It shows as 'skipped', not passed. Lots of disabled tests quietly remove coverage, so track and prune them.

open as a page

Why must a @Nested class be non-static, and how does that affect access to the outer test instance's state?

level: middleimportance: must knowfreq 50%
basics
~20 s

A @Nested class must be non-static so it holds a reference to an instance of the outer test class. That lets nested tests read and reuse the outer class's fields and setup. A static nested class would be independent and couldn't share that instance state.

open as a page

How do lifecycle methods like @BeforeEach and @AfterEach stack and execute across nesting levels in JUnit 5?

level: seniorimportance: must knowfreq 48%
basics
~20 s

Outer @BeforeEach methods run before inner ones, going from the outermost class inward, then the test runs, then @AfterEach methods run in reverse order — innermost first, outermost last. Each level layers its own setup and teardown around the test.

open as a page

What is JUnit 5's @Nested annotation, and why would you use it to organize your tests?

level: juniorimportance: should knowfreq 45%
basics
~20 s

@Nested marks an inner test class inside another test class. It groups related tests together so you can express scenarios like 'when the cart is empty' as their own block with their own setup, making tests easier to read and organize.

open as a page

When does @Nested improve a test suite versus harm it, and what design trade-offs guide how deeply you nest?

level: principalimportance: should knowfreq 30%
basics
~20 s

Use @Nested when tests fall into clear scenarios that share setup, so the report reads like sentences and duplication drops. Avoid nesting so deeply that setup becomes hard to follow or scenarios get artificial. Keep nesting shallow and meaningful.

open as a page

In JUnit 5, what is the difference between an assumption (assumeTrue) and an assertion (assertTrue)? What happens to a test when each one fails?

level: juniorimportance: must knowfreq 55%
basics
~20 s

An assertion checks the result you are testing: if it is false, the test FAILS. An assumption checks a precondition: if it is false, the test is SKIPPED (aborted), not failed. So a failed assumption is treated like a test that did not run.

open as a page

Walk through the assumeTrue and assumeFalse APIs in JUnit 5. What overloads exist (boolean vs BooleanSupplier, custom message), and what is a common use case?

level: middleimportance: should knowfreq 40%
basics
~20 s

assumeTrue(condition) keeps running the test only if the condition is true; assumeFalse(condition) keeps running only if it is false. Otherwise the test is skipped. Both can take a message, and both have a version that takes a BooleanSupplier so the condition is only evaluated lazily.

open as a page

How does assumingThat differ from assumeTrue in JUnit 5? Explain its two-argument form and when you would reach for it.

level: seniorimportance: should knowfreq 30%
basics
~20 s

assumeTrue aborts the whole test when its condition is false. assumingThat takes a condition AND a block of code (an Executable): it runs that block only if the condition is true, and otherwise just skips the block and lets the rest of the test continue normally. It does not abort the test.

open as a page

What are the consequences for test reporting, lifecycle callbacks, and coverage metrics when tests are aborted by assumptions? Why can heavy use of assumptions be risky at scale?

level: principalimportance: nice to knowfreq 18%
basics
~20 s

An aborted test counts as 'skipped', not 'passed' and not 'failed'. Lifecycle teardown still runs, but the test's body did not. Skipped tests do not exercise your code, so they contribute nothing to coverage. If many tests silently skip in CI, you can have a green build that actually tested very little.

open as a page

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%
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.

open as a page

Walk through the exact order in which JUnit 5 invokes constructor, @BeforeAll, @BeforeEach, @Test, @AfterEach, and @AfterAll for a two-test class — and how that sequence differs between PER_METHOD and PER_CLASS.

level: middleimportance: should knowfreq 42%
basics
~20 s

@BeforeAll runs once at the start, @AfterAll once at the end. Around every @Test, JUnit runs the constructor then @BeforeEach before, and @AfterEach after. In PER_METHOD the constructor runs once per test; in PER_CLASS it runs only once.

open as a page

What does @TestInstance(Lifecycle.PER_CLASS) change about how JUnit 5 runs a test class, and what does it enable?

level: middleimportance: should knowfreq 48%
basics
~10 s

@TestInstance(Lifecycle.PER_CLASS) tells JUnit to create just one instance of the test class and reuse it for every @Test. Because there's now a shared instance, @BeforeAll and @AfterAll can be non-static methods.

open as a page

How does the choice of test instance lifecycle interact with shared mutable state and test method ordering, and how do you keep PER_CLASS tests deterministic?

level: seniorimportance: should knowfreq 38%
basics
~20 s

PER_METHOD gives each test a fresh instance, so shared fields can't leak and order doesn't matter. PER_CLASS reuses one instance, so mutated fields persist between tests; to stay safe, reset that state in @BeforeEach and don't let tests depend on order.

open as a page

Why must @BeforeAll/@AfterAll be static under the default lifecycle, and what subtle bugs arise from mixing static fixture state with per-method test instances?

level: seniorimportance: nice to knowfreq 26%
basics
~20 s

Under the default PER_METHOD lifecycle JUnit makes a new instance per test, so there's no single instance to run a once-per-class method on — hence @BeforeAll/@AfterAll must be static (called on the class). The bug risk: static fixture fields are shared across all tests and aren't reset, so mutations leak between tests.

open as a page