What landed

Topic assertions

Use these when the outcome you care about is what landed on a topic, not one specific consumed record.

For field-by-field checks on one record, use Record assertions. For collection-style checks across many records, use Extraction assertions.

Entry point

Start with KafkaAssertions.assertThatTopic(topic) when you use @EnableWiretapKafkaBroker. Use the broker or bootstrap-server overloads only when you want to be explicit.

Java
import static io.typecheck.wiretap.core.assertions.KafkaAssertions.assertThatTopic;

orderService.createOrder("Widget", 200);

assertThatTopic("orders-topic")
    .exists()
    .hasRecordCount(1);

Listener readiness

Use readiness checks on input topics before publishing to a listener-driven flow. They wait for matching @KafkaListener containers to be running and assigned, but existing record-count and payload assertions do not wait implicitly.

PropertyDefaultDescription
hasReadyListeners()—waits for all listeners subscribed to this topic
hasReadyListeners(Duration)—same check with a custom timeout
hasReadyListeners(ids...)—waits for named listeners and verifies each subscribes to this topic
hasReadyListeners(Duration, ids...)—named listener checks with a custom timeout
hasReadyListener(id)—waits for one listener and verifies it subscribes to this topic
hasReadyListener(id, Duration)—named listener check with a custom timeout
Java
assertThatTopic("orders.in")
    .hasReadyListeners();

publisher.send("orders.in", "ord-1", order);

assertThatTopic("orders.out")
    .containsRecordWithKey("ord-1");
Java
assertThatTopic("orders.in")
    .hasReadyListeners("orderListener", "auditListener")
    .hasRecordCount(0);

Existence and topology

These assertions are payload-agnostic, so they do not need typed overloads.

PropertyDefaultDescription
exists()—proving a topic was created
doesNotExist()—negative setup checks
hasPartitionCount(int)—verifying topic shape
hasRecordCount(int)—exact publication count
hasRecordCountAtLeast(int)—background-activity-tolerant checks
isEmpty()—negative-path publication checks

Matcher-based scans

Keys

Java
assertThatTopic("orders-topic").containsRecordWithKey("order-123");
Java
assertThatTopic("orders-topic")
    .containsRecordWithKeyMatching(startsWith("order-"));

String payloads

Java
assertThatTopic("orders-topic").containsRecordWithValue("Widget");

Typed payloads (JSON, Avro & Protobuf)

Use the typed overloads when the topic contains DTOs, Protobuf Messages, Avro GenericRecord, or Avro SpecificRecord values and you want the library to deserialize them for you. JSON DTOs are compared structurally, so they don't need equals().

Java
OrderEvent expected = new OrderEvent("order-123", "customer-1", "Widget", 2, price, timestamp);

assertThatTopic("orders-topic")
    .containsRecordWithValue(expected);
Java
assertThatTopic("orders-topic")
    .containsRecordWithValue(OrderEvent.class, expected);
Java
assertThatTopic("orders-topic")
    .hasRecordCount(1)
    .containsRecordWithValueMatching(OrderEvent.class,
        hasProperty("quantity", equalTo(2)));

Reusing record assertions at topic scope

PropertyDefaultDescription
containsRecordSatisfying(...)—at least one matching record
allRecordsSatisfy(...)—every record must satisfy the same contract
noRecordSatisfies(...)—no record may satisfy a forbidden condition
Java
assertThatTopic("orders-topic")
    .containsRecordSatisfying(record -> record
        .hasKey("order-123")
        .hasValueMatching(containsString("Widget")));
Java
assertThatTopic("orders-topic")
    .allRecordsSatisfy(OrderEvent.class, record -> record
        .hasJsonPath("$.status", "CREATED")
        .hasValueMatching(hasProperty("product", containsString("Widget"))));

Advanced compositions

Common contract plus selected records

Combine a count with allRecordsSatisfy(...) when every event must share a contract, then add containsRecordSatisfying(...) checks for specific cases. Include a count when records must exist, because allRecordsSatisfy(...) passes for an empty topic.

Java
assertThatTopic("orders")
    .hasRecordCount(2)
    .allRecordsSatisfy(OrderEvent.class, record -> record
        .hasJsonPath("$.customerId", "cust-7")
        .hasHeader("X-Correlation-Id"))
    .containsRecordSatisfying(OrderEvent.class, record -> record
        .hasKey("order-123")
        .hasJsonPath("$.status", "CREATED"))
    .containsRecordSatisfying(OrderEvent.class, record -> record
        .hasKey("order-124")
        .hasJsonPath("$.status", "PAID"));

Exact distinct records

Two containsRecordSatisfying(...) calls can be satisfied by the same record when their conditions overlap. Use records(...).inAnyOrder().containingExactly(...)when the topic must contain distinct records matching an exact set of expectations.

Java
assertThatTopic("orders")
    .records(OrderEvent.class)
    .inAnyOrder()
    .containingExactly(
        record -> record
            .hasJsonPath("$.customerId", "cust-7")
            .hasKey("order-123")
            .hasJsonPath("$.status", "CREATED"),
        record -> record
            .hasJsonPath("$.customerId", "cust-7")
            .hasKey("order-124")
            .hasJsonPath("$.status", "PAID"));

Shared contract plus an exact set

When records share fields but differ in the business transition being asserted, check the common contract once and use exact-record matching for the varying fields.

Java
assertThatTopic("orders")
    .hasRecordCount(2)
    .allRecordsSatisfy(OrderEvent.class, record -> record
        .hasJsonPath("$.customerId", "cust-7"))
    .records(OrderEvent.class)
    .inAnyOrder()
    .containingExactly(
        record -> record.hasJsonPath("$.status", "CREATED"),
        record -> record.hasJsonPath("$.status", "PAID"));

Ordered records and snapshots

Use records() when the whole observed sequence matters. The typed overloads reuse the same JSON, Avro and Protobuf deserialization paths as the existing topic assertions, and failures include a table of observed records.

PropertyDefaultDescription
records()—starts an ordered assertion chain for String payloads
records(Class)—starts an ordered assertion chain for JSON DTO, Avro or Protobuf payloads
inOrder()—requires expectations to match topic read order
inAnyOrder()—allows exact expectations to match in any order
ignoringJsonPaths(...)—deletes volatile fields before snapshot comparison
containsExactlyJson(...)—compares normalized JSON payload snapshots
Java
assertThatTopic("orders-topic")
    .records(OrderEvent.class)
    .inOrder()
    .containingExactly(
        record -> record.hasJsonPath("$.status", "CREATED"),
        record -> record.hasJsonPath("$.status", "PAID"),
        record -> record.hasJsonPath("$.status", "SHIPPED"));
Java
assertThatTopic("orders-topic")
    .records(OrderEvent.class)
    .ignoringJsonPaths("$.createdAt", "$.traceId")
    .containsExactlyJson(expectedJson1, expectedJson2);

When to move to extraction assertions

Prefer Extraction assertions when the test really wants to say:

  • "these are the keys that were published"
  • "these are the payload field values that were published"
  • "all extracted values should satisfy a collection-style assertion"