Direct method calls check application decisions, while serialization, group assignment, offsets and redelivery require a running broker.
Spring Kafka testing layers: know what a direct listener test cannot prove
Keep each assertion at its layer
KafkaBoundaryContractTest calls the listener with a ConsumerRecord and a mock Acknowledgment. It checks a committed debit, duplicate event ID, conflicting replay, failed debit rollback, key mismatch, producer send arguments and a failed future. It also reflects on the listener annotation and configures a manual-ack factory object. These tests are fast, deterministic checks of local code. They do not start a Kafka listener container, deserialize bytes or commit an offset.
A container-level test should register the actual factory, serializers, error handler and listener bean against a disposable broker. It should publish a record, observe the committed business row, force a restart after the database commit but before acknowledgement, and verify that the same event ID does not debit twice. Add a poison record and check recovery publication plus offset movement. The general testing boundary warns against claiming deployment behavior from a narrow fixture.
Promote the real configuration to the test
Do not build one factory in the test and a different one in production. The fixture's factory assertion checks MANUAL_IMMEDIATE as an API setting but does not register it under the listener's named containerFactory. Wire that exact bean in the integration environment, use the deployed deserializer and schema version, then test with the target database engine if correctness depends on its constraint or locking behavior.
Code boundary
@Test
void insufficientStockRollsBackMarkerAndDoesNotAcknowledge() {
Acknowledgment acknowledgment = mock(Acknowledgment.class);
assertThrows(IllegalStateException.class,
() -> consumer.receive(record("EVT-48", "SKU-47", 20), acknowledgment));
verifyNoInteractions(acknowledgment);
assertEquals(19, available());
assertEquals(0, appliedCount());
}Verification boundary
The Java 21 / Spring Boot 4 source kit checks all eight methods in KafkaBoundaryContractTest, running without a broker.
Cost and limits
Direct tests run quickly but leave infrastructure behavior unobserved. Broker and production-database tests cost more startup time and resources; reserve them for the guarantees that require those components, especially offset recovery, partition assignment and concurrent deduplication.
Common Mistakes
- Do not treat a direct method call as proof of container registration.
- Do not treat a mock send completion as proof of Kafka acceptance.
- Do not omit restart and poison-record checks before promising at-least-once recovery.
Read next
Spring Kafka manual acknowledgement: commit work before advancing the offset, Spring Kafka producer future: observe broker send completion separately from command success, Spring Kafka listener failures: separate transient retries from poison records, Spring tests: separate business rules, wiring and transport, Spring consumer idempotency: reserve an event ID with the stock mutation.
