-
Notifications
You must be signed in to change notification settings - Fork 2
JSON Unit
Use these placeholders inside expected JSON (both in files and inline doc-strings) to validate dynamic response fields without knowing their exact value:
| Matcher | What it validates |
|---|---|
${json-unit.matches:isValidUUID} |
Value is a valid UUID |
${json-unit.matches:isValidDate} |
Value is parseable as a date |
${json-unit.matches:isValidIBAN} |
Value is a valid IBAN |
${json-unit.matches:isNotEqualTo}other string |
Value does not equal the string after the closing }
|
${json-unit.matches:isDateOfContext}<var> |
Value equals the date stored in ScenarioContext under <var> (date portion only) |
${json-unit.matches:isEqualToScenarioContext}<var> |
Value equals the string stored in ScenarioContext under <var>
|
${json-unit.matches:isNotEqualToScenarioContext}<var> |
Value does not equal the string stored in ScenarioContext under <var>
|
Example expected JSON file:
{
"id": "${json-unit.matches:isValidUUID}",
"name": "Alice",
"createdAt": "${json-unit.matches:isValidDate}",
"iban": "${json-unit.matches:isValidIBAN}",
"previousId": "${json-unit.matches:isNotEqualToScenarioContext}CREATED_ID"
}Beyond these matchers, JSON-Unit itself supports regex comparison, type placeholders, ignoring values/elements/paths, and more.
Note: Only unparameterized custom matchers or bdd lib matchers can be used for single-field validation (
I ensure that the body of the response contains a field … with the value …). Parameterized matchers require full-body file comparison.
Examples: bdd-cucumber-gherkin-lib/src/test/resources/features/body_validation/field_compare.feature
By default, JSON comparison is strict: extra fields, extra array elements, and array ordering all cause failures. Use the following steps (or annotations) to relax specific aspects.
Given that the response JSON can contain arrays with extra elementsThe comparison passes even if the response array has more elements than the expected JSON.
Annotation alternative (on Feature or Scenario):
@bdd_lib_json_ignore_new_array_elementsGiven that the response JSON can contain arrays in different orderThe comparison ignores the order of array elements.
Annotation alternative:
@bdd_lib_json_ignore_array_orderGiven that the response JSON can contain extra fieldsThe comparison passes even if the response contains fields not listed in the expected JSON.
Annotation alternative:
@bdd_lib_json_ignore_extra_fieldsExamples: bdd-cucumber-gherkin-lib/src/test/resources/features/flexible_json/
You can write your own JSON-Unit matchers and register them with the library.
A matcher must extend org.hamcrest.BaseMatcher and implement com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher.
import com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.springframework.stereotype.Component;
@Component
public class DividableByTwoMatcher extends BaseMatcher<Object> implements BddCucumberJsonMatcher {
@Override
public boolean matches(Object item) {
if (StringUtils.isNumeric(String.valueOf(item))) {
return Integer.parseInt((String) item) % 2 == 0;
}
return false;
}
@Override
public void describeTo(Description description) {}
@Override
public String matcherName() {
return "isDividableByTwo";
}
@Override
public Class<? extends BaseMatcher<?>> matcherClass() {
return this.getClass();
}
}Use it in an expected JSON:
{
"count": "${json-unit.matches:isDividableByTwo}"
}Implement net.javacrumbs.jsonunit.core.ParametrizedMatcher to receive a parameter.
For multiple arguments, pass them as a JSON string.
import com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher;
import net.javacrumbs.jsonunit.core.ParametrizedMatcher;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.springframework.stereotype.Component;
@Component
public class DividableByNumberMatcher extends BaseMatcher<Object>
implements ParametrizedMatcher, BddCucumberJsonMatcher {
private String parameter;
@Override
public boolean matches(Object item) {
if (StringUtils.isNumeric(String.valueOf(item))) {
return Integer.parseInt((String) item) % Integer.parseInt(parameter) == 0;
}
return false;
}
@Override
public void describeTo(Description description) {}
@Override
public String matcherName() { return "isDividableByNumber"; }
@Override
public Class<? extends BaseMatcher<?>> matcherClass() { return this.getClass(); }
@Override
public void setParameter(String parameter) { this.parameter = parameter; }
}Pass the parameter directly after the closing }:
{ "count": "${json-unit.matches:isDividableByNumber}5" }For a JSON argument (use single quotes inside):
{ "count": "${json-unit.matches:isDividableByNumber}{\"divisor\": 5}" }To add custom DateTimeFormatter patterns to ${json-unit.matches:isValidDate}, implement BddCucumberDateTimeFormat:
@Component
public class MyDateFormats implements BddCucumberDateTimeFormat {
@Override
public List<DateTimeFormatter> pattern() {
return List.of(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
}
}Register the class in @ContextConfiguration (see below).
Add custom matchers and formatters to the @ContextConfiguration classes in your test hooks:
@ContextConfiguration(classes = {
DividableByTwoMatcher.class,
DividableByNumberMatcher.class,
MyDateFormats.class
})See CreateContextHooks.java for a complete example. Custom matcher examples: src/test/java/com/ragin/bdd/cucumbertests/hooks/