[← 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)