skip to content

How do you assert on a JSON response body with MockMvcTester's bodyJson()?

level: seniorimportance: should knowfreq 50%

answer

  1. bodyJson() = same as @JsonTest JsonContentAssert
  2. extractingPath("$.x") JSONPath
  3. isLenientlyEqualTo vs isStrictlyEqualTo (JSONAssert modes)
  4. convertTo(Type) deserializes -> ObjectAssert
  5. expected can be a ClassPathResource

basics

~10 s

Call .bodyJson() after a request to get a JSON assert. Use .extractingPath("$.field") with JSONPath to check individual values, .convertTo(Type.class) to deserialize, or .isLenientlyEqualTo(...)/.isStrictlyEqualTo(...) to compare whole documents.

solid answer

~40 s

After performing a request, .bodyJson() returns an AbstractJsonContentAssert (the same JSON assertion used by Spring Boot's @JsonTest). Three main styles: (1) JSONPath extraction — .extractingPath("$.name").isEqualTo("Alice") or .hasPathSatisfying("$.items[0].id", v -> ...); (2) whole-document comparison against expected JSON — .isLenientlyEqualTo(expectedJson) ignores extra fields and ordering, .isStrictlyEqualTo(...) requires an exact match (both use JSONAssert semantics); (3) object conversion — .convertTo(Person.class) deserializes the body and returns an AssertJ ObjectAssert so you can assert on typed fields. You can source expected JSON from a classpath resource. bodyJson needs a JSONPath library (bundled via spring-test) and, for convertTo, the tester's HttpMessageConverters. Prefer JSONPath/convertTo for targeted checks; strict equality is brittle when payloads evolve.

code

java · 16 lines
java
// Targeted JSONPath
assertThat(mvc.get().uri("/users/1"))
        .hasStatusOk()
        .bodyJson()
        .extractingPath("$.name").isEqualTo("Alice");

// Whole-document, lenient (extra fields allowed)
assertThat(mvc.get().uri("/users/1"))
        .bodyJson()
        .isLenientlyEqualTo(new ClassPathResource("expected/user.json"));

// Convert to a typed object and assert on fields
assertThat(mvc.get().uri("/users/1"))
        .bodyJson()
        .convertTo(User.class)
        .satisfies(u -> assertThat(u.name()).isEqualTo("Alice"));

go deeper

for a junior

Know bodyJson() plus extractingPath to check a single field.

for a middle

Use lenient vs strict comparison and convertTo, and know expected JSON can be a classpath resource.

for a senior

Choose the right style per test, explain JSONAssert lenient/strict semantics, and align converters for convertTo.

for a principal

Establish JSON-assertion conventions (favor JSONPath/lenient, snapshot fixtures) to keep contract tests robust across teams.

## Getting the JSON assert After a request, call `.bodyJson()` on the `MvcTestResultAssert`. It returns an **`AbstractJsonContentAssert`** — the very same JSON assertion type Spring Boot exposes in `@JsonTest` via `JsonContentAssert`. This unifies how you assert JSON in web tests and serializer tests. ## Style 1 — JSONPath extraction (targeted) - `.extractingPath("$.name")` returns a **`JsonPathValueAssert`**; chain `.isEqualTo("Alice")`, `.isNotNull()`, `.asString()...`, `.convertTo(Type.class)`, etc. - `.hasPathSatisfying("$.items[0].id", value -> value.assertThat().isEqualTo(42))` asserts a nested value with a callback. - `.doesNotHavePath("$.password")` / `.hasPath("$.id")` assert presence/absence. JSONPath (`$.a.b[0]`) is the query language here; it needs the Jayway JSONPath library, which ships with spring-test / the Boot test starter. ## Style 2 — whole-document comparison - `.isLenientlyEqualTo(expected)` — **lenient**: extra fields in the actual body are allowed and array/field ordering is ignored (JSONAssert `LENIENT` mode). Robust to additive changes. - `.isStrictlyEqualTo(expected)` — **strict**: exact structural match, no extra fields (JSONAssert `STRICT`). Brittle when the payload grows. - `.isEqualTo(expected)` with an explicit `JsonCompareMode` also available. - `expected` can be an inline JSON String **or** a classpath resource (e.g., `.isLenientlyEqualTo(new ClassPathResource("expected/user.json"))`), which keeps large fixtures out of the test code. ## Style 3 — convert to a typed object - `.convertTo(Person.class)` deserializes the whole body and returns an AssertJ `ObjectAssert<Person>`; then assert on typed getters: `.satisfies(p -> assertThat(p.name()).isEqualTo("Alice"))` or `.extracting(Person::name).isEqualTo("Alice")`. - `.convertTo(InstanceOfAssertFactories.list(Item.class))` for arrays. - Conversion uses the tester's configured `HttpMessageConverters` (Jackson by default), so it honors your custom modules/date formats when you set them via `withHttpMessageConverters(...)`. ## When to use which - **JSONPath / convertTo** for precise, evolution-tolerant checks — assert only what matters. - **Lenient equality** when you want a broad shape check but expect additive changes. - **Strict equality** only for stable contract snapshots (or you'll fight false failures on every new field). ## Gotchas - `.bodyJson()` on a non-JSON body throws / fails parsing — assert content type first if unsure. - Strict equality is a classic source of brittle tests; prefer JSONPath. - `convertTo` needs matching converters — a custom Jackson `ObjectMapper` in production but default in the tester can cause mismatches; configure `withHttpMessageConverters`. - For plain text bodies use `.bodyText()` / `.body().asString()`, not `.bodyJson()`.

  • When would you choose isStrictlyEqualTo over isLenientlyEqualTo?
    Strict for a stable, snapshot-style contract where extra/missing fields must fail the test. Lenient (or JSONPath) when the payload may gain fields over time and you only care about a subset — strict equality otherwise breaks on every additive change.
  • Your .convertTo(...) deserializes dates differently than production. What's the fix?
    The tester isn't using your production HttpMessageConverters/ObjectMapper. Configure it via MockMvcTester.withHttpMessageConverters(...) so JSON (de)serialization matches, including Jackson modules and date formats.

saying these in an interview costs you the question

  • Defaulting to strict equality and getting brittle tests
  • Using bodyJson() on a non-JSON body
  • Assuming convertTo uses production converters without configuring them

context