Unravel โบ Java concepts โบ Testing
Advanced JUnit 5 and async assertions
Beyond simple @Test methods, JUnit 5 supports parameterised tests, nested test groups, temporary directories, conditional execution and custom extensions.
Learn these first
At a glance
A test lab with a template: instead of writing the same experiment fifty times with different samples, you write it once and feed it a table of samples, and the lab prints a separate result for each.
- 1Parameterised tests: @ParameterizedTest with @CsvSource({"1,2,3", "0,0,0"}), @ValueSource, @EnumSource or @MethodSource("cases") run the same body for each row and report each separately
- 2@Nested inner classes group scenarios ("when the cart is empty", "when the cart has items") with shared set-up; @TempDir Path dir gives a clean temporary directory; @Tag("slow") filters runs
- 3assertAll(() -> ..., () -> ...) reports every failure instead of stopping at the first; assertThrows returns the exception for further checks; AssertJ (assertThat(list).hasSize(2).extracting(...)) gives fluent, readable assertions
- 4await().atMost(5, SECONDS).untilAsserted(() -> assertEquals(3, repo.count())) polls until the condition holds or fails with a clear message, instead of fixed sleeps
- Thread.sleep in tests is either too short (flaky failures) or too long (slow suite)
- Each test must be independent of order
- The JUnit Platform discovers tests through engines; the Jupiter engine builds a tree of containers and test descriptors from annotations, evaluates conditions, instantiates the test class per method (default) and runs the extension callbacks in a defined order around each phase
@ParameterizedTest@CsvSource({"100,10,90", "50,0,50", "20,100,0"})void appliesDiscount(int price, int percent, int expected) { assertEquals(expected, Pricing.apply(price, percent));}@Testvoid eventuallyProcessesMessage() { publisher.publish(new OrderPlaced("o-1")); await().atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> assertTrue(orders.exists("o-1")));}- line 1 uses ParameterizedTest, the core of Advanced JUnit 5 and async assertions
- line 2 uses CsvSource, the core of Advanced JUnit 5 and async assertions
Read the full explanation (about 2 min)
Beyond simple @Test methods, JUnit 5 supports parameterised tests, nested test groups, temporary directories, conditional execution and custom extensions. Awaitility replaces Thread.sleep for code that finishes asynchronously.
A mental model
A test lab with a template: instead of writing the same experiment fifty times with different samples, you write it once and feed it a table of samples, and the lab prints a separate result for each.
How it works
Parameterised tests: @ParameterizedTest with @CsvSource({"1,2,3", "0,0,0"}), @ValueSource, @EnumSource or @MethodSource("cases") run the same body for each row and report each separately.
@Nested inner classes group scenarios ("when the cart is empty", "when the cart has items") with shared set-up; @TempDir Path dir gives a clean temporary directory; @Tag("slow") filters runs.
assertAll(() -> ..., () -> ...) reports every failure instead of stopping at the first; assertThrows returns the exception for further checks; AssertJ (assertThat(list).hasSize(2).extracting(...)) gives fluent, readable assertions.
await().atMost(5, SECONDS).untilAsserted(() -> assertEquals(3, repo.count())) polls until the condition holds or fails with a clear message, instead of fixed sleeps.
Going deeper
Thread.sleep in tests is either too short (flaky failures) or too long (slow suite). Awaitility finishes as soon as the condition is true and has a bounded timeout.
Each test must be independent of order. Shared mutable state (static fields, databases not cleaned) causes tests to pass alone and fail together; @TestInstance(PER_CLASS) and @Order make this easy to introduce on purpose and by accident.
Custom extensions (ParameterResolver, BeforeEachCallback) are the right place for reusable set-up, replacing base classes and JUnit 4 rules.
Under the hood
The JUnit Platform discovers tests through engines; the Jupiter engine builds a tree of containers and test descriptors from annotations, evaluates conditions, instantiates the test class per method (default) and runs the extension callbacks in a defined order around each phase.
Common mistakes
- Sleeping instead of awaiting a condition.
- Order-dependent tests.
- One assertion failure hiding others.
- Parameterised tests with unreadable data tables.
How Unravel recognises it
When you paste code, Unravel looks for these signs of advanced junit 5 and async assertions. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@ParameterizedTest@Nested@RepeatedTest@TestFactory@TestInstance@TempDir@Tag@Disabled@EnabledIf@EnabledOnOs@EnabledIfEnvironmentVariable@DisabledIf@MethodSource@CsvSourceand 6 more - Types:
AwaitilityParameterResolverBeforeEachCallbackAfterEachCallbackTestExecutionExceptionHandlerSoftAssertionsDynamicTestExtensionContext - Method calls:
assertAll()assertTimeout()assertTimeoutPreemptively()assertDoesNotThrow()assertThrowsExactly()
Part of a learning path
- Testing enterprise code ยท previous: JUnit tests ยท next: Mockito mocks
Related concepts
Frequently asked questions
What is Advanced JUnit 5 and async assertions?
Beyond simple @Test methods, JUnit 5 supports parameterised tests, nested test groups, temporary directories, conditional execution and custom extensions. Awaitility replaces Thread.sleep for code that finishes asynchronously.
What are the most common mistakes with Advanced JUnit 5 and async assertions?
Sleeping instead of awaiting a condition. Order-dependent tests. One assertion failure hiding others. Parameterised tests with unreadable data tables.
How can I see Advanced JUnit 5 and async assertions in real code?
Paste your own Java into Unravel and it will point out where advanced junit 5 and async assertions appears, explain how it works at the detail level you choose, and draw the flow of the code around it.