skip to content

How do JSONPath assertions differ between MockMvc and WebTestClient?

level: seniorimportance: should knowfreq 40%

answer

  1. same JSONPath syntax, different DSL
  2. MockMvc: static jsonPath -> JsonPathResultMatchers -> andExpect
  3. WebTestClient: expectBody().jsonPath -> JsonPathAssertions
  4. equality: .value() vs .isEqualTo()
  5. 6.2 MockMvcWebTestClient bridge

basics

~10 s

The JSONPath expression syntax is identical, but the assertion APIs differ. MockMvc uses jsonPath("$.x").value(...) (JsonPathResultMatchers) inside andExpect. WebTestClient uses .expectBody().jsonPath("$.x").isEqualTo(...) (JsonPathAssertions), a fluent chain with no static import.

solid answer

~30 s

Both share Jayway JSONPath expression syntax, so `$.field`, `$[0]`, `$[*].id` mean the same thing. The difference is the assertion surface. MockMvc's `jsonPath(...)` is a **static factory** on `MockMvcResultMatchers` returning `JsonPathResultMatchers` with methods like `.value(...)`, `.exists()`, `.isArray()`, `.isEmpty()`, wrapped in `.andExpect(...)`. WebTestClient's is an **instance method** reached via `.expectBody().jsonPath(...)`, returning `JsonPathAssertions` with a fluent, slightly different vocabulary: `.isEqualTo(...)`, `.exists()`, `.doesNotExist()`, `.isArray()`, `.isNotEmpty()`, `.value(...)`. So MockMvc says `jsonPath("$.name").value("Ada")` while WebTestClient says `.jsonPath("$.name").isEqualTo("Ada")`. WebTestClient is the reactive/WebFlux client (also usable over MockMvc via bindToController), returning `WebTestClient.BodyContentSpec`; MockMvc is servlet-based. Same query language, different assertion DSL.

code

java · 14 lines
java
// MockMvc (servlet)
mockMvc.perform(get("/users/1"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.name").value("Ada"))
    .andExpect(jsonPath("$.roles").isArray());

// WebTestClient (fluent) — same expressions, different API
webTestClient.get().uri("/users/1")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .jsonPath("$.name").isEqualTo("Ada")   // note: isEqualTo, not value
    .jsonPath("$.roles").isArray()
    .jsonPath("$.age").value(greaterThan(18)); // value() takes a Hamcrest matcher

go deeper

for a junior

Knows both exist and use $-style paths; may not recall API differences.

for a middle

Can write both forms and remembers .value vs .isEqualTo.

for a senior

Explains the type-level difference (JsonPathResultMatchers vs JsonPathAssertions) and the 6.2 bridge.

for a principal

Chooses the right client per stack and standardizes one testing DSL across MVC and WebFlux where sensible.

**Shared foundation:** Both frameworks evaluate the **same Jayway JSONPath** expressions against the response body. Any expression — `$`, `$.name`, `$.address.city`, `$.items[0]`, `$.items[*].id`, `$.items.length()` — works identically on both. What differs is the surrounding assertion API and how you obtain the JSONPath entry point. **MockMvc (servlet):** - Type: `org.springframework.test.web.servlet.result.JsonPathResultMatchers`, produced by the static factory `MockMvcResultMatchers.jsonPath(String, Object... args)` (statically imported). - Each method returns a `ResultMatcher` consumed by `ResultActions.andExpect(...)`. - Vocabulary: `.value(Object)`, `.value(Matcher)`, `.value(Matcher, Class targetType)`, `.exists()`, `.doesNotExist()`, `.isEmpty()`, `.isNotEmpty()`, `.isArray()`, `.isMap()`, `.isString()`, `.isNumber()`, `.isBoolean()`. - Shape: `mockMvc.perform(get(...)).andExpect(jsonPath("$.name").value("Ada"))`. **WebTestClient (reactive-style):** - Type: `org.springframework.test.web.reactive.server.JsonPathAssertions`, reached fluently: `client.get().uri(...).exchange().expectStatus().isOk().expectBody().jsonPath("$.name").isEqualTo("Ada")`. - No static import; `jsonPath` is an instance method on `WebTestClient.BodyContentSpec`. - Vocabulary: `.isEqualTo(Object)`, `.value(Matcher<T>)`, `.value(Consumer<T>)`, `.value(Class<T>, Matcher)`, `.exists()`, `.doesNotExist()`, `.isEmpty()`, `.isNotEmpty()`, `.isArray()`, `.isMap()`, `.isBoolean()`, `.isNumber()`. Note the equality method is `.isEqualTo(...)`, not `.value(...)`. - Chaining: each assertion returns the assertions/body spec so you keep chaining fluently rather than adding separate `andExpect` calls. **Where each applies:** WebTestClient is the standard client for **WebFlux** apps and can also bind to a running server, a `RouterFunction`, or a controller (`bindToController`). As of Spring Framework 6.2 WebTestClient can even wrap **MockMvc** (`MockMvcWebTestClient.bindTo(...)`), letting servlet apps use the fluent DSL. MockMvc itself is servlet-only (Spring MVC). **Gotchas:** - Muscle-memory trap: writing `.value("Ada")` in WebTestClient for equality — it exists but the idiomatic equality method is `.isEqualTo("Ada")`; `.value(...)` there takes a Hamcrest matcher or a consumer, not a plain expected value in the same way. - `content().json(...)` also exists on WebTestClient as `.expectBody().json(String)` with lenient/strict via `JsonCompareMode` (6.2+), mirroring MockMvc. - `JsonPathResultMatchers` is MockMvc-specific; you will not find that exact type in the WebTestClient path (it's `JsonPathAssertions`). Conflating the two type names is a common slip. - Both support the same number-typing subtleties from Jayway. **When to use:** MockMvc for Spring MVC controller slice tests; WebTestClient for WebFlux, for full end-to-end HTTP against a running server, or when you prefer the fluent DSL (including over MockMvc via the 6.2 bridge).

  • In WebTestClient, what does `.jsonPath("$.age").value(...)` take, versus `.isEqualTo(...)`?
    `.isEqualTo(expected)` asserts simple equality with an expected value. `.value(...)` takes a Hamcrest `Matcher` (or a `Consumer` for custom assertions, optionally with a target `Class`), enabling non-equality checks like `greaterThan` or `hasSize`.
  • Can a Spring MVC (servlet) app use WebTestClient's fluent JSONPath DSL?
    Yes. Since Spring Framework 6.2, `MockMvcWebTestClient.bindTo(...)` (or bindToController/bindToApplicationContext) lets you drive MockMvc through WebTestClient's fluent API without a running server, so servlet tests can use `.expectBody().jsonPath(...)`.

saying these in an interview costs you the question

  • Claiming the JSONPath expression syntax differs between the two
  • Using MockMvc's `andExpect(jsonPath(...))` inside a WebTestClient chain
  • Saying `JsonPathResultMatchers` is the WebTestClient assertion type (it's JsonPathAssertions)

context