In a Karate feature file with `* def date = { month: '3' }`, `match date == { month: '#? _ > 0' }` passes but `match date == { month: '#number? _ > 0' }` fails. Why?
answer
- Two checks, and the order matters
- One value is a string, not a number
- JavaScript comparison is not a type check
- A name may sit before the question mark
basics
~20 sThe month value is the string '3'. The bare predicate passes because JavaScript coerces it to a number before comparing. The combined form runs the number validator first, and that validator does not coerce, so a string fails.
solid answer
~40 sA `#`-marker may carry a validator name before the `?` and a JavaScript expression after it, and Karate runs them **in that order** — validator first, predicate only if the validator passed. `'#? _ > 0'` names no validator, so nothing checks the type: the expression alone decides, and JavaScript's relational operators coerce, so the string `'3'` compares as `3` and the node passes. `'#number? _ > 0'` puts the number validator in front, and that validator is a plain type test with no conversion step — a string is not a number, so it fails with `not a number` before the expression is ever evaluated. The combined form is usually what you want, because it pins the type and the business rule in one expected value.
code
gherkin · 7 lines* def date = { month: '3' }
# passes: JavaScript coerces the string before comparing
* match date == { month: '#? _ > 0' }
# fails with 'not a number': the validator runs first and does not coerce
* match date == { month: '#number? _ > 0' }go deeper
Notice that the two markers differ by one word and that the payload holds a quoted value. Being able to spot that a JSON number and a JSON string are not the same thing is what is being tested here.
Explain the split at the question mark, the short circuit when the validator fails, and JavaScript's coercion in relational comparison. Say which form you would write by default and why.
Treat a suite full of bare predicates as a type-drift blind spot. Know that green tests over string-encoded numbers are a real failure mode, and be able to say when the looseness is deliberate.
Decide how strictly payload types are pinned across many suites. Total strictness makes every upstream serialisation change a test change; total looseness hides the changes that matter, so the line is drawn per field class, not per team.
## Anatomy of the marker string A `#`-marker in an expected payload has up to two parts, and the question mark is the seam: ``` # validatorName ? javascript expression ``` Both parts are optional, which is why three spellings of the same idea look so similar and behave so differently: | Expected value | What runs | |---|---| | `'#number'` | the number validator only | | `'#? _ > 0'` | the expression only | | `'#number? _ > 0'` | the validator, then the expression | Karate splits the string at the first `?`, looks the leading name up in its validator table, and keeps the rest as JavaScript. ## Order of operations The order is fixed and it is what the question turns on: 1. If there is a name before the `?`, the matching validator runs against the actual value. 2. If that validator fails, the node fails immediately and the expression is **never evaluated**. 3. Only if the validator passed — or there was no name at all — is the JavaScript expression evaluated with `_` bound to the value. 4. The node passes when the expression returns true. So `'#number? _ > 0'` is a conjunction with a short circuit, not two independent opinions blended together. ## Why the bare predicate accepts the string `month` holds `'3'`, a JSON **string**. The expression `_ > 0` is ordinary JavaScript, and JavaScript's relational operators do not compare types — they coerce both operands toward numbers before ordering them. `'3'` converts to `3`, `3 > 0` is true, and the node passes. The test is green, and it is green for a reason that has nothing to do with the payload being correct. That coercion is a feature when you mean it. `'#? _ > 0'` is the right marker when a field is legitimately serialised as a string and you only care about its magnitude. It is a trap when you assumed the API returns a number and never checked. ## Why the number validator rejects it The validators are plain type tests over the parsed JSON value, with no conversion step: - the number validator asks whether the value is a number, and a `String` is not; - the string validator asks whether it is a string; - the array and object validators ask about a list and a map. There is nothing here that would turn `'3'` into `3` first. The node fails with `not a number`, the report shows the actual value `'3'` beside the marker, and the predicate never runs. ## The general form Any validator name can carry a predicate, and the pattern is worth making a habit: ```gherkin * match order == """ { id: '#string? _.startsWith("ORD-")', quantity: '#number? _ >= 1', tags: '#array? _.length <= 5', createdAt: '#string? isIsoDate(_)' } """ ``` Each line pins a type *and* a rule. Reading the payload tells you the contract; running it enforces both halves. Splitting that into a type-only match plus a pile of separate assertions loses the single failure report and doubles the lines to maintain. ## Where this bites in practice The mismatch shows up wherever a service is loose about JSON types: - money and decimals serialised as strings to dodge floating point; - identifiers that are numeric today and alphanumeric after the next migration; - a field that arrives as a number from one upstream and a string from another, depending on which shard answered. A suite written entirely with bare predicates stays green through all of that, because JavaScript keeps quietly coercing. A suite written with the combined form fails the day the type changes, which is exactly when you want to hear about it. If a field is *genuinely* allowed to be either, say so — a bare predicate, with a comment explaining that the looseness is deliberate, is honest; an accidental one is not. ## Choosing between them - Use the **combined form** by default. It is one extra word and it states the whole contract. - Use a **bare predicate** when the type really is not part of the expectation, or when the value under test is the whole payload and a type marker adds nothing. - Use a **type marker alone** when there is no rule, only a shape.
- Which other validators can carry a predicate after the `?`Any of them — the name in front of the `?` is looked up in the same validator table, so `'#string? _.startsWith("ORD-")'`, `'#array? _.length <= 5'` and `'#object? _.id != null'` all work. The validator runs first, the expression second, and both have to pass for the node to pass.
- The API returns `{ month: '3' }` and you want the suite to fail on that. What do you write?`'#number? _ > 0'` if the range still matters, or the bare `'#number'` if it does not. Either fails on a quoted value, because the validator is a type test over the parsed JSON. Reserve the bare predicate for fields where a string form is genuinely acceptable, and say so in a comment so the looseness reads as deliberate.
saying these in an interview costs you the question
- Says the two forms are interchangeable
- Thinks the number validator coerces the value the way JavaScript does
- Believes the predicate runs before the type check
- Assumes a quoted JSON number still satisfies the number validator
- Reaches for a JSON Schema document instead of the combined marker