Covers replacing Thread.sleep with await() across a void @Async method, an @EmbeddedKafka-backed @KafkaListener, and an already-running @Scheduled job; verifies Awaitility 4.3.0's real defaults (10s timeout, 100ms poll interval) and its default-uncaught-exception-handler swap directly against the jar; and documents a real pom.xml trap where Boot 4.1.1 split Kafka's autoconfiguration (spring-boot-kafka) out of spring-kafka itself, which silently leaves @KafkaListener beans with no running container.
85 lines
5.3 KiB
Markdown
85 lines
5.3 KiB
Markdown
# awaitility
|
|
|
|
Companion code for the Awaitility article on [ankurm.com](https://ankurm.com). All intermediate
|
|
and reference-grade depth lives in the post itself (accordions / "going deeper" paragraphs), not
|
|
in a `docs/NN-topic.md` chapter folder — the one exception, as in the rest of this repository, is
|
|
`docs/output/`, which holds real captured transcripts and nothing else.
|
|
|
|
## What this is
|
|
|
|
Six real, runnable demonstrations of the same idea: a fixed `Thread.sleep(...)` in a test is a
|
|
guess about timing the test does not control, and `Awaitility.await()` replaces the guess with a
|
|
bounded poll loop. Each demonstration targets a different source of asynchrony:
|
|
|
|
| Test | What it waits for |
|
|
|---|---|
|
|
| `AwaitAsyncConfirmationTest` | A `void` `@Async` method with no `Future` to block on |
|
|
| `DefaultTimingTest` | Nothing — it proves Awaitility's own defaults (10s timeout, 100ms poll interval) against the real jar |
|
|
| `KafkaListenerAwaitTest` | An `@KafkaListener` consuming a record a `KafkaTemplate` just sent, against a real in-process `@EmbeddedKafka` broker |
|
|
| `ScheduledJobAwaitTest` | Three more executions of an already-running `@Scheduled(fixedRate = 150)` job |
|
|
| `UncaughtExceptionHandlerSwapTest` | Nothing — it proves `await()` temporarily installs its own `Thread.setDefaultUncaughtExceptionHandler` and restores the original afterward |
|
|
| `IgnoreExceptionsTest` | A resource that throws for its first 300ms, using `ignoreExceptionsInstanceOf(...)` to treat that as "not yet", not a failure |
|
|
|
|
Two more transcripts in `docs/output/` are real failures from tests that are *not* in the suite
|
|
above: `01-sleep-guesses-wrong.txt` (a fixed `Thread.sleep(100)` against a 220ms operation) and
|
|
`08-exception-propagates-immediately.txt` (the same resource as `IgnoreExceptionsTest`, minus
|
|
`ignoreExceptionsInstanceOf(...)`). Both were real `mvn test` runs, captured once, then the
|
|
failing test class was deleted — the mistake is preserved as a transcript, not as a permanently
|
|
red test.
|
|
|
|
## The pom.xml trap this module exists to document
|
|
|
|
The first version of this module's `pom.xml` depended on `org.springframework.kafka:spring-kafka`
|
|
directly, the way every pre-Boot-4.1 tutorial does. It compiled. Every `@SpringBootTest` using
|
|
Kafka then failed with an empty `ConcurrentLinkedQueue` and no error at context startup, because
|
|
in Boot 4.1.1 the Kafka autoconfiguration classes (`KafkaTemplate`, `ConsumerFactory`, the
|
|
`KafkaListenerEndpointRegistry` that `@KafkaListener` needs) moved out of the monolithic
|
|
`spring-boot-autoconfigure` jar into their own module, `org.springframework.boot:spring-boot-kafka`
|
|
— pulled in by the new `org.springframework.boot:spring-boot-starter-kafka`, not by `spring-kafka`
|
|
alone. See `pom.xml`'s own comments and the post for the full diagnosis.
|
|
|
|
## Versions
|
|
|
|
Read from `spring-boot-dependencies-4.1.1.pom` and `spring-boot-starter-test-4.1.1.pom` on Maven
|
|
Central, not from release notes: **JDK 25** (Temurin 25.0.4.1+1), **Spring Boot 4.1.1**,
|
|
**Spring Kafka 4.1.1**, **Awaitility 4.3.0** (already on the classpath via
|
|
`spring-boot-starter-test` — no explicit `<dependency>` for it anywhere in `pom.xml`).
|
|
|
|
## Quickstart
|
|
|
|
```
|
|
mvn test
|
|
```
|
|
|
|
11 tests, 0 failures. `KafkaListenerAwaitTest` starts a real in-process Kafka broker
|
|
(`@EmbeddedKafka`) and takes a few seconds; `DefaultTimingTest` deliberately waits out Awaitility's
|
|
real 10-second default timeout once, so the suite as a whole takes about 30 seconds.
|
|
|
|
## Captured output
|
|
|
|
| File | What it's from |
|
|
|---|---|
|
|
| `00-full-test-run.txt` | The full `mvn test` run, 11/11 green |
|
|
| `01-sleep-guesses-wrong.txt` | Real failure: a guessed `Thread.sleep(100)` against a 220ms operation (test since removed) |
|
|
| `02-await-finds-it.txt` | The fixed version: `await().untilAsserted(...)` against the same operation |
|
|
| `03-default-timeout-is-ten-seconds.txt` | `await().until(() -> false)` timing out at ~10,000ms with no override |
|
|
| `04-default-poll-interval-is-100ms.txt` | Real poll timestamps, ~100ms apart, with no override |
|
|
| `05-kafka-listener-await.txt` | `await()` for an `@KafkaListener` to consume a record just sent |
|
|
| `06-scheduled-job-await.txt` | `await()` for 3 more executions of an already-running `@Scheduled` job |
|
|
| `07-uncaught-exception-handler-swap.txt` | Proof that `await()` swaps and restores the JVM's default uncaught-exception handler |
|
|
| `08-exception-propagates-immediately.txt` | Real failure: an exception from the polled condition, with no `ignoreExceptionsInstanceOf(...)`, failing on the first poll (test since removed) |
|
|
| `09-ignore-exceptions-waits-it-out.txt` | The fixed version: the same resource, with `ignoreExceptionsInstanceOf(...)` |
|
|
| `10-missing-kafka-starter-failure.txt` | Real failure: the Kafka test with `spring-kafka` as a direct dependency instead of `spring-boot-starter-kafka` (pom.xml since fixed) |
|
|
| `11-pom-diagnosis.txt` | The real `curl`/`grep` transcript that found the Boot 4.1 Kafka module split against Maven Central's own POMs |
|
|
|
|
## What's sourced from documentation, not run here
|
|
|
|
Awaitility's own `@since` Javadoc tags, Spring Boot's own migration notes for the Kafka module
|
|
split, and the general shape of `@KafkaListener`/`@EmbeddedKafka` wiring are cited from the real
|
|
jars and POMs on Maven Central (see the post for exact artifact coordinates), not re-derived from
|
|
scratch in this module.
|
|
|
|
## Licence
|
|
|
|
MIT — see [LICENSE](../LICENSE).
|