How do `content().string(...)` and the Hamcrest matcher overload of `jsonPath(...)` let you write richer assertions?
answer
- content().string = raw body + Hamcrest
- jsonPath(...).value(Matcher) = expressive field checks
- hasSize / greaterThan / containsInAnyOrder
- value(Matcher, targetType) coerces type
- string() brittle on JSON — reserve for text
basics
~10 scontent().string(Matcher) runs a Hamcrest matcher against the raw body string (e.g. containsString("ok")). jsonPath("$.list").value(hasSize(3)) passes a Hamcrest matcher against a selected JSON value for flexible, non-equality checks.
solid answer
~30 s`content().string(...)` takes either an exact `String` or a Hamcrest `Matcher<? super String>` and applies it to the **raw response body text** — useful for `containsString(...)`, `startsWith(...)`, or matching non-JSON bodies. Separately, `jsonPath(...)` has a `.value(Matcher<T>)` overload that applies a Hamcrest matcher to the value extracted at that path, unlocking assertions beyond equality: `jsonPath("$.items").value(hasSize(3))`, `jsonPath("$.age").value(greaterThan(18))`, `jsonPath("$.tags").value(containsInAnyOrder("a","b"))`, `jsonPath("$.name").value(equalToIgnoringCase("ada"))`. This combines JSONPath's navigation with Hamcrest's expressive matcher vocabulary. Use `content().string` when you care about the whole body or it isn't JSON; use `jsonPath(...).value(matcher)` for structural/relational checks on a specific field or collection.
code
java · 12 linesimport static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
mockMvc.perform(get("/users"))
.andExpect(jsonPath("$").value(hasSize(3)))
.andExpect(jsonPath("$[0].age").value(greaterThan(18)))
.andExpect(jsonPath("$[*].name").value(containsInAnyOrder("Ada", "Alan", "Grace")))
// coerce extracted value to Integer before matching
.andExpect(jsonPath("$[0].id").value(is(1), Integer.class));
mockMvc.perform(get("/health"))
.andExpect(content().string(containsString("UP")));go deeper
Knows containsString on the body and that .value can take a matcher.
Fluently mixes JsonPath navigation with Hamcrest (hasSize, greaterThan, containsInAnyOrder).
Knows the value(Matcher, targetType) coercion overload and why string-matching JSON is brittle.
Guides the team toward jsonPath/json over string matchers and standardizes collection assertions.
**Hamcrest recap:** Hamcrest is a matcher library (`org.hamcrest.Matchers`) providing composable predicates like `is`, `containsString`, `startsWith`, `greaterThan`, `hasSize`, `hasItem`, `hasItems`, `containsInAnyOrder`, `equalToIgnoringCase`, `not`, `hasEntry`. Spring's result matchers accept these for expressive assertions. **`content().string(...)`:** `content()` returns `ContentResultMatchers`. Two `string` overloads: - `string(String expected)` — asserts the raw body equals the string exactly. - `string(Matcher<? super String> matcher)` — applies a Hamcrest matcher to the raw body text. Example: `content().string(containsString("\"status\":\"UP\""))` or, for a plain-text endpoint, `content().string(startsWith("OK"))`. This operates on the **unparsed** body, so it's ideal for non-JSON responses or coarse substring checks, but it's fragile for structured JSON (whitespace/order sensitive) — prefer `jsonPath`/`content().json` there. **`jsonPath(...).value(Matcher)`:** `JsonPathResultMatchers.value` is overloaded: `value(Object expected)` for equality and `value(Matcher<T> matcher)` for Hamcrest. The matcher runs against the object JsonPath extracts: - Scalars: `jsonPath("$.age").value(greaterThan(18))`, `jsonPath("$.name").value(equalToIgnoringCase("ada"))`. - Collections: `jsonPath("$.items").value(hasSize(3))`, `jsonPath("$.tags").value(hasItem("kotlin"))`, `jsonPath("$.tags").value(containsInAnyOrder("a","b","c"))`. - There's also a generically-typed `value(Matcher<T>, Class<T> targetType)` overload to coerce the extracted value to a target type before matching (helps with the number-typing problem). **Gotchas:** - Type coercion: `jsonPath("$.id").value(is(1))` works, but if the extracted value is a `Double` (e.g. `1.0`) and you match `is(1)` (Integer), it fails. Use the `targetType` overload or match the actual runtime type. - `hasSize` needs the path to resolve to a collection/array; against a scalar it fails. - `content().string(containsString(...))` on JSON is brittle — an unrelated formatting change or field reorder can break it. Reserve it for genuine substring/non-JSON needs. - Static imports: `import static org.hamcrest.Matchers.*;` alongside the MockMvc result-matcher import. **When to use which:** `content().string` — whole-body or non-JSON assertions. `jsonPath(...).value(matcher)` — relational/structural assertions on a navigated field or collection. `jsonPath(...).value(expected)` — simple field equality.
- When would `content().string(containsString(...))` be a poor choice for a JSON endpoint?It matches raw text, so it's sensitive to whitespace, key ordering, and unrelated field changes, and can match a substring in the wrong place. For structured JSON use `jsonPath` or `content().json`; reserve string matchers for plain-text bodies or genuine substring needs.
- How do you assert a JSON array has exactly 3 elements?`jsonPath("$.items").value(hasSize(3))` using Hamcrest's `hasSize`. Alternatively `jsonPath("$.items.length()").value(3)` uses JsonPath's built-in `length()` function.
saying these in an interview costs you the question
- Using `content().string` with substring checks as the primary way to assert JSON fields
- Thinking `.value()` only supports exact equality, not Hamcrest matchers
- Assuming `hasSize` works on a scalar value