skip to content

How do `alwaysDo(...)` and `alwaysExpect(...)` on a MockMvc builder work, and when would you use them?

level: seniorimportance: should knowfreq 34%

answer

  1. always* set on the builder, run every perform()
  2. alwaysDo -> ResultHandler (print/log everywhere)
  3. alwaysExpect -> ResultMatcher (shared invariant)
  4. augments, not replaces, per-request andDo/andExpect
  5. over-broad alwaysExpect breaks 204/error/non-JSON

basics

~10 s

They register a ResultHandler (alwaysDo) or ResultMatcher (alwaysExpect) once on the builder, and it runs automatically on every request performed by that MockMvc — so you don't repeat .andDo(...)/.andExpect(...) in each test.

solid answer

~40 s

Both are configured on a `ConfigurableMockMvcBuilder` (via `webAppContextSetup`/`standaloneSetup`) before `build()`. `alwaysDo(ResultHandler)` attaches a handler that runs on every `perform(...)` — commonly `alwaysDo(print())` or `alwaysDo(log())` so every request is dumped for debugging without repeating `.andDo(...)`. `alwaysExpect(ResultMatcher)` attaches an assertion that runs on every request — useful for invariants shared across a controller's tests, e.g. `alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))` or a standard security header. You can register several; they run in addition to any per-request `.andDo(...)`/`.andExpect(...)`. Caveat: `alwaysExpect` must genuinely hold for *every* endpoint the MockMvc instance hits — error responses, 204 No Content, or non-JSON paths will break an over-broad global matcher, so keep global expectations to true universals.

code

java · 18 lines
java
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;

@BeforeEach
void setUp(WebApplicationContext context) {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .alwaysDo(log())  // every request dumped at DEBUG, no per-test .andDo
            .alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
            .build();
}

@Test
void getUser() throws Exception {
    // content-type is asserted globally; only the specifics remain here
    mockMvc.perform(get("/api/users/1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.name").value("alice"));
}

go deeper

for a junior

Know they let you avoid repeating .andDo/.andExpect on every test.

for a middle

Explain alwaysDo=ResultHandler, alwaysExpect=ResultMatcher, set on the builder.

for a senior

Reason about which invariants are safe as globals and the 204/error-path pitfalls.

for a principal

Set suite conventions (log() everywhere, minimal global matchers) and handle @AutoConfigureMockMvc via MockMvcBuilderCustomizer.

**Where they live.** `alwaysDo` and `alwaysExpect` are methods on `ConfigurableMockMvcBuilder` (the interface behind `MockMvcBuilders.webAppContextSetup(...)` and `standaloneSetup(...)`). You call them while configuring the builder, before `.build()`. They set defaults that apply to *every* request performed by the resulting `MockMvc`. **`alwaysDo(ResultHandler)`** — registers a `ResultHandler` (same type used by `.andDo(...)`) to run after every request. The canonical use is diagnostics: `alwaysDo(print())` or, better for committed suites, `alwaysDo(log())`, so you never sprinkle `.andDo(print())` across dozens of tests. You can register more than one; each is invoked per request. **`alwaysExpect(ResultMatcher)`** — registers a `ResultMatcher` (same type used by `.andExpect(...)`) to assert on every request. Use it for invariants that hold across an entire controller/test class: a shared response content type, a standard header (e.g. a security or cache header your filter always adds), or a common structural check. These run in addition to per-test `.andExpect(...)`. **Ordering / composition.** Global (`always*`) handlers and matchers run together with the per-request ones for each `perform`. Multiple globals run in registration order. They do not replace per-request expectations — they augment them. **Design guidance (why be careful).** - `alwaysExpect` is a footgun if the invariant is not truly universal. If one endpoint returns `204 No Content` (no body, no content type) or an error path returns `application/problem+json` while your global says `application/json`, the global matcher fails those tests spuriously. Restrict global matchers to genuine cross-cutting truths, or scope MockMvc instances per controller so the invariant really holds. - `alwaysDo(print())` on a large suite creates enormous, noisy output; prefer `alwaysDo(log())` (DEBUG-gated) so it is silent unless you flip the log level. - These are builder-scoped: they affect only that `MockMvc` instance. With `@AutoConfigureMockMvc`, you get a pre-built instance and cannot call `always*` on it directly — you'd customize via a `MockMvcBuilderCustomizer` bean, or build MockMvc manually to use them. **When to use.** - `alwaysDo(log())`: uniform, opt-in debugging across a test class. - `alwaysExpect(...)`: enforce a real, class-wide contract (content type, mandatory header) without repetition, when you are confident it holds for every request that instance makes.

  • What is the risk of `alwaysExpect(content().contentType(APPLICATION_JSON))` across a whole app's MockMvc?
    Any endpoint that returns 204 No Content, a redirect, a non-JSON media type, or an error payload like application/problem+json will fail the global matcher. Scope it to controllers where JSON truly always applies.
  • Can you call `alwaysDo`/`alwaysExpect` on the MockMvc injected by `@AutoConfigureMockMvc`?
    Not directly — those are builder methods and you receive a built instance. Register a MockMvcBuilderCustomizer bean, or build MockMvc manually via webAppContextSetup to use them.

saying these in an interview costs you the question

  • Saying alwaysExpect replaces per-test andExpect (it augments them)
  • Using alwaysExpect for non-universal invariants and blaming flakiness
  • Calling always* on an already-built MockMvc instance
  • Preferring alwaysDo(print()) over log() in large committed suites

context