Avro Payloads

Contract Testing compares Avro message payloads in consumer contracts with Avro schemas in AsyncAPI provider contracts. Each payload goes through a value check and a structure check. Between them, the checks catch changes that break the consumer, including changes that keep the payload valid but break Avro decoding.For how comparison works for all payload formats, see Features - Testing AsyncAPI.

Requirements

Consumer Contract

  • Write the consumer contract with the Pact Avro plugin. The plugin embeds the consumer's Avro schema in the contract, and Contract Testing uses it for the structure check.

  • Use Pact Specification Version 4.

  • The interaction's content type must be avro/binary, avro/bytes or application/avro. The plugin sets it for you.

  • Reference the AsyncAPI operation in the same way as for JSON messages:

{
  "type": "Asynchronous/Messages",
  "comments": {
    "references": {
      "AsyncAPI": { "operationId": "receiveUserSignedUp" }
    }
  },
  "contents": {
    "contentType": "avro/binary;record=com.example.UserSignedUp"
  }
}

Provider Contract

  • Define the message payload as a Multi Format Schema Object, with schemaFormat set to application/vnd.apache.avro, application/vnd.apache.avro+json or application/vnd.apache.avro+yaml. A version parameter is allowed, for example application/vnd.apache.avro;version=1.9.0.

  • Embed the Avro schema in the AsyncAPI definition. Contract Testing doesn't follow references to external files or schema registries.

  • The schema can be a record, or a union of records. The consumer's record must match one of them by name.

components:
  messages:
    UserSignedUp:
      contentType: avro/binary
      payload:
        schemaFormat: application/vnd.apache.avro;version=1.9.0
        schema:
          type: record
          name: UserSignedUp
          namespace: com.example
          fields:
            - name: userId
              type: string
            - name: email
              type: ["null", "string"]
              default: null

How Avro Payloads Are Compared

Value Check

Contract Testing decodes the consumer's example message and validates it against the provider's schema, in the same way as a JSON payload.

Structure Check

A change to an Avro schema can keep the example valid and still break the consumer. For example, if the provider changes a field from int to long, the consumer's decoder can no longer read the message. The structure check catches these changes.

The structure check uses Avro schema resolution. The writer encodes a message with its schema. The reader decodes it with its own schema and resolves the differences:

  • Fields are matched by name or alias.

  • Fields the reader doesn't have are skipped.

  • Fields the writer doesn't send take the reader's default value.

  • Types must match, or follow Avro's promotion rules. For example, a reader that expects long can read an int.

Which side is the writer depends on the interaction:

InteractionWriter

Writer

Reader

Fire-and-forget message

Provider

Consumer

Request/reply: the request

Consumer

Provider

Request/reply: the reply

Provider

Consumer

The structure check assumes that the reader decodes with the writer's schema, for example by looking it up in a schema registry. This is how Avro schema resolution works.

All fields in the consumer's schema are compared. If the consumer's schema is a projection that has only the fields the consumer uses, only those fields are compared. The consumer's example is used only to decide whether a default value or an enum symbol matters to the consumer.

Structure Check Rules

Errors are reported as message.payload.incompatible, and warnings as message.payload.risky. Where a rule applies, the result message ends with the rule name in parentheses.

Change

Result

Rule

The consumer's record isn't one the provider's message carries

Error (message.payload.type.incompatible)

N/A

The reader expects a field the writer doesn't send, and the reader's schema has no default

Error

READER_FIELD_MISSING_DEFAULT_VALUE

The consumer expects a field the provider no longer sends. The consumer gets the default value, but its example expects a different value

Error

N/A

The reader expects a field the writer doesn't send, and the reader's schema has a default

Warning

N/A

The writer's type can't be read as the reader's type, for example long read as int, or a field made nullable

Error

TYPE_MISMATCH

The writer's union has a branch the reader's union can't read

Error

MISSING_UNION_BRANCH

A record, enum or fixed type has a different name on each side

Error

NAME_MISMATCH

A fixed type has a different size on each side

Error

FIXED_SIZE_MISMATCH

The writer's enum has symbols the reader's enum doesn't have, and the reader's enum has no default

Error

MISSING_ENUM_SYMBOLS

The writer's enum has symbols the reader's enum doesn't have, and the reader's enum has a default

Warning

N/A

The consumer's example uses an enum symbol the provider no longer has

Error

N/A

The consumer sends a field the provider doesn't read, so the provider ignores it

Warning

N/A

Limitations

  • Embedded schemas only: the Avro schema must be in the AsyncAPI definition. Contract Testing doesn't query schema registries. If the schema is missing or can't be resolved, the result is message.payload.unvalidatable.

  • Consumer schema required: if the consumer contract doesn't contain the Avro schema, the result is message.payload.unvalidatable. Write the contract with the Pact Avro plugin.

  • Decoding assumption: the structure check assumes consumers decode with the writer's schema. If your consumers decode with their own schema only, the results may not match what happens at runtime.

  • Large numbers: long values beyond 2^53 (9,007,199,254,740,992) are rounded to the nearest representable number before the value check. A rounded value could pass or fail a check that the exact value wouldn't.

  • Warnings don't fail verification: a message.payload.unvalidatable result means the payload wasn't compared. The message still counts as a match.

Publication date: