# Built-in Matchers

> Reference for all built-in RecordMatcher implementations and when to use each.

Source: https://docs.ktestify.xyz/docs/extend/matchers/built-in-matchers

KTestify ships 12 `RecordMatcher<V>` implementations, split into raw (String) and Avro (GenericRecord) variants. They are selected automatically by `RecordMatcherFactory` based on the `matchMethod` in `MatchContext`.

---

## Factory resolution table

| `matchMethod` constant | Raw matcher | Avro matcher |
|---|---|---|
| `methodMatchFile` | `FileRecordMatcher` | `AvroFileRecordMatcher` |
| `methodMatchKeyValue` | `FileKeyRecordMatcher` | `AvroFileKeyRecordMatcher` |
| `methodFieldsToMatch` | `FieldsRecordMatcher` | `AvroFieldsRecordMatcher` |
| `methodMatchXML` | `XmlRecordMatcher` | ❌ `ConsumerException` |
| `methodMatchXPath` | `XPathRecordMatcher` | ❌ `ConsumerException` |
| `methodRecordKeyMatch` | `KeyRecordMatcher` | `AvroKeyRecordMatcher` |
| `methodMatchAttributes` | `AttributeRecordMatcher<V>` | ❌ `ConsumerException` |
| `null` / blank | `NoOpRecordMatcher<V>` | `NoOpRecordMatcher<V>` |

```java
// Usage
RecordMatcher<String> raw  = RecordMatcherFactory.forRaw("matchFile");
RecordMatcher<GenericRecord> avro = RecordMatcherFactory.forAvro("matchFile");
```

---

## Raw matchers (V = String)

### `FileRecordMatcher`

Compares record values character-for-character against expected file content after dynamic variable resolution. Supports both single-record and multi-record batch matching (`POSITIONAL` and `LAZY` 1-to-1 matching without replacement). Uses `StringDiffUtils` to produce a coloured ANSI diff on failure.

- **MatchContext fields used:** `matchFilePaths`, `matchKeys`, `keyMatchStrategy`, `batchMatchStrategy`
- **On failure:** returns `MatchResult.fail(diff, expected, actual)`
- **On count mismatch:** throws `ComparisonException` when `records.size() != matchFilePaths.size()`

---

### `FileKeyRecordMatcher`

Asserts both the record **key** and **value**. The expected file must contain the key on line 1 and the value from line 2 onwards. Key matching uses the `KeyMatchStrategy` from `MatchContext` (defaults to `EXACT`).

- **MatchContext fields used:** `matchFilePaths.get(0)`, `keyMatchStrategy`

---

### `KeyRecordMatcher`

Asserts only the record **key**, the value is not evaluated. Key matching uses the `KeyMatchStrategy` from `MatchContext` (defaults to `EXACT`).

- **MatchContext fields used:** `matchKey` (falls back to `matchFilePaths.get(0)` if blank), `keyMatchStrategy`

---

### `FieldsRecordMatcher`

Extracts a character range `[from, to)` from line `line` of the record value and compares it against the same range from the expected file. Implemented via `FieldMatcherUtils`.

- **MatchContext fields used:** `matchFilePaths.get(0)`, `matchKey` (encodes `line:from:to`)

---

### `XmlRecordMatcher`

Parses both the record value and the expected file as XML, optionally removes excluded elements from both, then compares structurally using `XMLUtils`. Element order does not matter.

- **MatchContext fields used:** `matchFilePaths.get(0)`, `excludedFields` (element name list)
- **Not available for Avro.**

---

### `XPathRecordMatcher`

Runs one or more XPath expressions against the record value and compares each extracted value against the corresponding line of the expected file.

- **MatchContext fields used:** `matchFilePaths.get(0)`, `matchKey` (comma-separated XPath expressions)
- **Not available for Avro.**

---

### `NoOpRecordMatcher<V>`

Always returns `MatchResult.pass()`. Used when `matchMethod` is `null` or blank, typically for watcher steps that only care about record presence, not content.

---

### `AttributeRecordMatcher<V>` (`1.1.1`)

Generic, transport agnostic matcher introduced alongside `RequestResponseClient<Req, V>` for synchronous transports. Asserts one or more entries of `ConsumedRecord.getAttributes()` against `MatchContext.getExpectedAttributes()`. Never inspects `getValue()`, so it works for any `V`, not only `String`.

- **MatchContext fields used:** `expectedAttributes` (map of key to expected value)
- **Matching rule:** every key in `expectedAttributes` must be present in the actual record's `attributes` with an exactly equal String value, only the first record in the list is used
- **On empty `expectedAttributes`:** returns `MatchResult.pass()` immediately, nothing to assert
- **Typical use:** HTTP status code assertions (`{"statusCode": "200"}`), reusable later by any future transport that populates `attributes` (gRPC status, MQ reason code, script exit code)
- **Not available for Avro**, `attributes` is populated by synchronous request/response clients, not Avro consumers

---

## Avro matchers (V = GenericRecord)

All Avro matchers first deserialise the `GenericRecord` to a JSON `Map` via `AvroUtils.toJsonMap()`, then delegate to comparison logic equivalent to their raw counterparts.

### `AvroFileRecordMatcher`

Avro equivalent of `FileRecordMatcher`. Converts `GenericRecord` to a JSON string, excludes fields listed in `excludedFields`, then compares against the expected file(s). Supports both single-record and multi-record batch matching (`POSITIONAL` and `LAZY` 1-to-1 matching without replacement).

- **MatchContext fields used:** `matchFilePaths`, `matchKeys`, `keyMatchStrategy`, `excludedFields`, `batchMatchStrategy`
- **On count mismatch:** throws `ComparisonException` when `records.size() != matchFilePaths.size()`

---

### `AvroFileKeyRecordMatcher`

Avro equivalent of `FileKeyRecordMatcher`. Asserts key + Avro-serialised JSON value. Key matching uses the `KeyMatchStrategy` from `MatchContext` (defaults to `EXACT`).

---

### `AvroKeyRecordMatcher`

Avro equivalent of `KeyRecordMatcher`. Key-only assertion; Avro value ignored. Key matching uses the `KeyMatchStrategy` from `MatchContext` (defaults to `EXACT`).

---

### `AvroFieldsRecordMatcher`

Avro equivalent of `FieldsRecordMatcher`. Extracts a character range from the JSON-serialised Avro value.

---

## Key design rules

1. `excludedFields` defaults to `Collections.emptyList()`, matchers check `isEmpty()`, never `null`.
2. `matchFilePaths` is always a `List<String>`, single-record matchers use `getFirst()`, and batch matchers (`FileRecordMatcher`, `AvroFileRecordMatcher`) compare all records against `matchFilePaths` using `batchMatchStrategy` (`POSITIONAL` by default, or `LAZY` for unordered 1-to-1 matching).
3. XML/XPath matchers throw `ConsumerException` when called via `RecordMatcherFactory.forAvro()`.
4. `NoOpRecordMatcher` is the default when no match method is configured.
5. `expectedAttributes` defaults to `Collections.emptyMap()`, `AttributeRecordMatcher` checks `isEmpty()`, never `null`, same convention as `excludedFields`.
6. `keyMatchStrategy` defaults to `KeyMatchStrategy.EXACT`, key matchers (`KeyRecordMatcher`, `FileKeyRecordMatcher`, `AvroKeyRecordMatcher`, `AvroFileKeyRecordMatcher`) and the fetch-time key filter (`KafkaRecordFetcher.passesKeyFilter()`) both delegate to `KeyMatchStrategy.matches(expected, actual)`. Available strategies: `EXACT`, `CONTAINS`, `STARTS_WITH`, `ENDS_WITH`, `REGEX`.
7. `batchMatchStrategy` defaults to `BatchMatchStrategy.POSITIONAL`. Available strategies: `POSITIONAL` and `LAZY`.
