Companion repository for the ankurm.com article. Every transcript in docs/output/ was produced by running this project; scripts/run-all.sh regenerates all of them. Verified against Spring Boot 4.1.1 / Framework 7.0.9 / Security 7.1.1 / Micrometer 1.17.1 / kafka-clients 4.2.1 on Temurin JDK 25.0.4.1+1.
96 lines
3.5 KiB
Markdown
96 lines
3.5 KiB
Markdown
[← 08 The diagnostics endpoint](08-diagnostics.md) · **09 · Testing Actuator** · [README →](../README.md)
|
|
|
|
# 09 — Testing Actuator
|
|
|
|
Eight tests, and none of them assert the happy path. They pin the behaviour that would be
|
|
expensive to rediscover after an upgrade.
|
|
|
|
## Pin the surprises
|
|
|
|
[`ActuatorExposureTests`](../src/test/java/com/ankurm/actuator/ActuatorExposureTests.java) runs
|
|
with `exposeall,secured` — the widest exposure with real security — and asserts:
|
|
|
|
```java
|
|
@Test
|
|
void heapdumpIsNotExposedEvenWithWildcardExposure() throws Exception {
|
|
mvc().perform(get("/actuator/heapdump")).andExpect(status().isNotFound());
|
|
}
|
|
```
|
|
|
|
If a future Boot release changes the `access` default for `heapdump`, or if somebody adds
|
|
`management.endpoint.heapdump.access=unrestricted` to a shared config, this test is where you
|
|
find out. Same for `shutdown`.
|
|
|
|
```java
|
|
@Test
|
|
void envMasksEveryValueRegardlessOfKeyName() throws Exception {
|
|
// acme.partner.credential matches no password/secret/token pattern.
|
|
... .andExpect(jsonPath("$.property.value").value("******"));
|
|
}
|
|
|
|
@Test
|
|
void anonymousHealthCarriesNoComponentBreakdown() throws Exception {
|
|
mvc().perform(get("/actuator/health"))
|
|
.andExpect(jsonPath("$.components").doesNotExist());
|
|
}
|
|
```
|
|
|
|
That last one is the `show-details: when-authorized` contract from
|
|
[chapter 04](04-securing-actuator.md), asserted rather than assumed. It is a one-line
|
|
configuration change away from leaking your dependency map to anonymous callers.
|
|
|
|
## Test the operational contract, not the indicator
|
|
|
|
[`HealthGroupTests`](../src/test/java/com/ankurm/actuator/HealthGroupTests.java):
|
|
|
|
```java
|
|
@Test
|
|
void upstreamOutageMovesTheExternalApiIndicatorButNotLiveness() {
|
|
upstream.set(UpstreamState.Mode.DOWN);
|
|
assertThat(statusOf("externalApi")).isEqualTo(Status.DOWN);
|
|
assertThat(statusOf("livenessState")).isEqualTo(Status.UP);
|
|
}
|
|
```
|
|
|
|
That is [chapter 07](07-groups-and-probes.md) as an executable assertion. It fails if someone
|
|
adds `externalApi` to the liveness group, which is the exact change that causes a fleet-wide
|
|
restart loop.
|
|
|
|
```java
|
|
@Test
|
|
void theExternalApiIndicatorRespectsItsTimeoutBudget() {
|
|
upstream.set(UpstreamState.Mode.SLOW); // the stub sleeps 30s
|
|
...
|
|
assertThat(elapsedMs).isLessThan(5_000L);
|
|
}
|
|
```
|
|
|
|
Fails if someone removes the read timeout. A generous bound, because timing assertions on CI are
|
|
how you get flaky suites — but 5 s versus 30 s is a wide enough gap to be safe.
|
|
|
|
## `DEFINED_PORT`, and why
|
|
|
|
```java
|
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
|
|
```
|
|
|
|
Not the default `MOCK` environment. `ExternalApiHealthIndicator` makes a real HTTP call to this
|
|
application's own stub controller, so a servlet container has to be listening on the port it was
|
|
configured with. Under `MOCK` the indicator reports `DOWN` with a connection error and every
|
|
assertion about upstream state is meaningless — which is precisely what happened on the first
|
|
run of this suite.
|
|
|
|
`RANDOM_PORT` does not help here: the indicator resolves its URL at bean construction, before
|
|
the server binds, so `local.server.port` is not available to it.
|
|
|
|
## What is not tested
|
|
|
|
There is no test for the 60-second Kafka timeout. A test that takes a minute to pass does not
|
|
belong in a suite people run before pushing —
|
|
[`scripts/demo-kafka-timeout.sh`](../scripts/demo-kafka-timeout.sh) captures it instead, and the
|
|
transcript is committed. Some evidence is better as a script than as an assertion.
|
|
|
|
---
|
|
|
|
[← 08](08-diagnostics.md) · **09** · [README →](../README.md)
|