Skip to main content

Spring MockMvc

Spring MockMvc has traditionally used Hamcrest matchers for assertions, which required mixing different assertion styles and was less discoverable. Spring Framework 6.2 introduced MockMvcTester, providing full AssertJ integration for more consistent and expressive controller tests.

Traditional Hamcrest matchers

MockMvc's traditional approach uses MockMvcResultMatchers with Hamcrest-style matchers, which feels disconnected from modern assertion libraries.

BundleControllerTest.java
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class BundleControllerTest {

private final MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new BundleController()).build();

@Test
void getBundle() throws Exception {
mockMvc.perform(get("/bundle"))
.andExpectAll(
status().isOk(),
content().contentType(MediaType.APPLICATION_JSON),
jsonPath("$.books[0].title").value("Effective Java"));
}

@Test
void boom() throws Exception {
mockMvc.perform(get("/boom"))
.andExpect(status().isInternalServerError());
}
}
warning

Traditional MockMvc assertions:

  • Use Hamcrest-style matchers instead of AssertJ
  • Require many static imports from MockMvcResultMatchers
  • Mix assertion styles when combining with other AssertJ assertions
  • Less discoverable API compared to fluent AssertJ chains
  • Force checked exception handling with throws Exception

Bridge approach

For incremental migration, you can use MockMvcTester while keeping existing MockMvcRequestBuilders imports.

BundleControllerTest.java
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class BundleControllerTest {

private final MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new BundleController()).build();

@Test
void getBundle() throws Exception {
mockMvc.perform(get("/bundle"))
.andExpectAll(
status().isOk(),
content().contentType(MediaType.APPLICATION_JSON),
jsonPath("$.books[0].title").value("Effective Java"));
}

@Test
void boom() {
assertThat(mockMvc.perform(get("/boom")))
.hasStatus5xxServerError();
}
}
info

Starting with traditional MockMvc and Hamcrest matchers.

JSON path assertions

Testing JSON responses becomes more expressive with AssertJ integration.

BundleControllerTest.java
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@Test
void verifyJsonContent() throws Exception {
mockMvc.perform(get("/bundle"))
.andExpectAll(
jsonPath("$.books[0].title").value("Effective Java"),
jsonPath("$.books[0].author").value("Joshua Bloch"),
jsonPath("$.books[0].year").value(2001),
jsonPath("$.books").isArray(),
jsonPath("$.books.length()").value(3));
}
warning

Hamcrest-style JSON path assertions:

  • Each path requires a separate jsonPath() call
  • Mixing different matcher types (value(), isArray())
  • Less fluent and harder to chain
  • Limited type safety

Status code assertions

Status assertions are more semantic and expressive with MockMvcTester.

BundleControllerTest.java
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Test
void statusCodes() throws Exception {
mockMvc.perform(get("/ok")).andExpect(status().isOk());
mockMvc.perform(get("/created")).andExpect(status().isCreated());
mockMvc.perform(get("/not-found")).andExpect(status().isNotFound());
mockMvc.perform(get("/error")).andExpect(status().isInternalServerError());
}
info

Traditional status matchers work but require the status() wrapper.