skip to content

How do you assert on status, headers, and body with MockMvcResultMatchers, and how do content() and jsonPath() differ?

level: middleimportance: must knowfreq 70%

answer

  1. status()/header()/content()/jsonPath()
  2. content().json = whole doc, lenient by default
  3. jsonPath = single fields + Hamcrest
  4. andExpectAll aggregates failures
  5. contentTypeCompatibleWith dodges charset traps

basics

~10 s

Chain andExpect with MockMvcResultMatchers: status().isOk() for the code, header().string("Location", "/x") for headers, and content().string(...)/content().json(...) or jsonPath("$.field").value(...) for the body.

solid answer

~30 s

MockMvcResultMatchers provides typed matcher factories you pass to andExpect. status() covers the HTTP code — isOk(), isCreated(), isNotFound(), is4xxClientError(), or is(418). header() asserts response headers — header().string(name, value) or header().exists(name). For the body you have two families: content() operates on the raw response — content().contentType(MediaType.APPLICATION_JSON), content().string(exact), content().json(expectedJson) (order-insensitive semantic JSON compare), content().xml(...). jsonPath() evaluates a JSONPath expression against the JSON body — jsonPath("$.name").value("Ada"), jsonPath("$.items", hasSize(3)), jsonPath("$.items[0].id").value(7). Use content().json for whole-document equality; use jsonPath for surgical field-level assertions and Hamcrest matchers. andExpectAll(...) runs several matchers and reports all failures instead of stopping at the first.

code

java · 13 lines
java
mockMvc.perform(post("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\":\"Ada\"}"))
       .andExpectAll(
           status().isCreated(),
           header().string("Location", "/users/42"),
           content().contentType(MediaType.APPLICATION_JSON),
           jsonPath("$.id").exists(),
           jsonPath("$.name").value("Ada"));

// Whole-document form:
mockMvc.perform(get("/users/42"))
       .andExpect(content().json("{\"id\":42,\"name\":\"Ada\"}"));

go deeper

for a junior

Knows status().isOk() and content().string.

for a middle

Fluently mixes status/header/content/jsonPath and knows json is lenient.

for a senior

Chooses content().json vs jsonPath deliberately and handles charset/strictness gotchas.

for a principal

Sets team conventions (assert shape once vs field-by-field) and reasons about brittle-test trade-offs.

**`MockMvcResultMatchers`** is the static-factory class whose methods produce `ResultMatcher` objects — each verifies one aspect of the `MvcResult`. You feed them to `ResultActions.andExpect(...)`. It is almost always static-imported so calls read as `status()`, `content()`, `header()`, `jsonPath(...)`. **1. Status — `status()`** returns a `StatusResultMatchers`: - Named helpers: `isOk()` (200), `isCreated()` (201), `isNoContent()` (204), `isBadRequest()` (400), `isUnauthorized()` (401), `isForbidden()` (403), `isNotFound()` (404). - Ranges: `is2xxSuccessful()`, `is4xxClientError()`, `is5xxServerError()`. - Arbitrary code: `is(418)`. - `reason(...)` asserts the status reason phrase (for `sendError`). **2. Headers — `header()`** returns `HeaderResultMatchers`: - `header().string("Location", "/users/42")` — exact value. - `header().string("Cache-Control", containsString("max-age"))` — with a Hamcrest matcher. - `header().exists(name)` / `header().doesNotExist(name)`. - `header().longValue(...)`, `header().dateValue(...)` for typed comparisons. **3. Body — two distinct families:** **`content()`** (a `ContentResultMatchers`) operates on the *raw response payload and its content type*: - `content().contentType(MediaType.APPLICATION_JSON)` — asserts the `Content-Type` header (with charset compatibility via `contentTypeCompatibleWith`). - `content().string("exact body")` or `content().string(Matcher)` — raw string comparison. - `content().json("{\"name\":\"Ada\"}")` — **semantic** JSON comparison: field order doesn't matter, and by default it's *lenient* (extra fields in the actual response are allowed; pass `strict=true` to require an exact set). - `content().xml(...)` and `content().node(...)` for XML. - `content().encoding(...)`. **`jsonPath(expr)`** (returns `JsonPathResultMatchers`) evaluates a **JSONPath** expression against the JSON body and asserts on the extracted value: - `jsonPath("$.name").value("Ada")` — leaf equality. - `jsonPath("$.name").value(startsWith("A"))` — Hamcrest matcher. - `jsonPath("$.items").isArray()`, `jsonPath("$.items", hasSize(3))`. - `jsonPath("$.items[0].id").value(7)` — indexing and nested paths. - `jsonPath("$.token").exists()` / `.doesNotExist()` / `.isNotEmpty()`. - Filters: `jsonPath("$.items[?(@.active==true)]").exists()`. **When to use which:** `content().json(...)` is best when you want to assert the *entire* document shape at once. `jsonPath(...)` is best for a few important fields, for arrays/collections with size/element checks, or when values are dynamic (assert `exists()` on a generated id rather than a fixed value). **Fail-fast vs. fail-all:** `andExpect` stops at the first failing matcher. `andExpectAll(m1, m2, ...)` evaluates *all* matchers and aggregates failures, giving a fuller picture in one run — useful when several assertions might fail together. **Gotchas:** - `jsonPath` requires the body to actually be JSON; a non-JSON body throws a parse error, not a clean assertion failure. - `content().json` leniency means a passing test doesn't prove the response has *no* extra fields — use `strict` or explicit `doesNotExist` when that matters. - `content().contentType(MediaType.APPLICATION_JSON)` can fail on charset mismatch (`application/json;charset=UTF-8`); prefer `contentTypeCompatibleWith(MediaType.APPLICATION_JSON)` when charset varies. - `header().string` fails if the header is absent; use `header().exists` first when a header is optional.

  • Is content().json strict or lenient by default?
    Lenient — extra fields in the actual response are allowed and field order is ignored. Pass content().json(expected, JsonCompareMode.STRICT) (or the boolean overload) to require an exact field set.
  • How do you assert a JSON array has three elements?
    jsonPath("$.items", hasSize(3)) using the Hamcrest hasSize matcher, or jsonPath("$.items.length()").value(3).

saying these in an interview costs you the question

  • Thinking content().json requires exact field match by default (it's lenient)
  • Using content().string for JSON and fighting whitespace/order instead of jsonPath/content().json
  • Believing jsonPath works on any body type regardless of format

context