Spring MVC provides MockMvc as the primary mechanism for testing controllers without starting a full HTTP server (last tutorial). Traditionally, assertions were written using the Hamcrest matcher library, which requires chaining calls through andExpect(). While functional, this style mixes request building and assertion logic in a way that can be hard to read and navigate with IDE auto-completion.
Starting with Spring Framework 6.2, a new class MockMvcTester was introduced as part of the org.springframework.test.web.servlet.assertj package. It offers full AssertJ support, enabling a fluent, type-safe assertion style that is consistent with the rest of the testing stack. AssertJ's assertThat()-based API provides better IDE auto-completion, cleaner given/when/then test structure, and more descriptive failure messages.
Why AssertJ over Hamcrest for MockMvc?
Prior to Spring Framework 6.2, writing a simple controller test looked like this with Hamcrest:
mockMvc.perform(get("/greeting"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello")));
The new AssertJ-based approach using MockMvcTester rewrites the same test as:
assertThat(mockMvcTester.get().uri("/greeting"))
.hasStatusOk()
.bodyText().contains("Hello");
The benefits of the AssertJ style include: no need for multiple static imports from MockMvcResultMatchers, a consistent fluent API driven by IDE auto-completion, and a cleaner separation of the request setup from the assertion phase when using .exchange() to capture MvcTestResult first.
How it works
MockMvcTester is the entry point. It wraps an underlying MockMvc instance and returns an AssertJ-compatible result when a request is executed.
MockMvcTester.MockMvcRequestBuilder builder = mockMvc.get()
.uri("/greeting");
MockMvcRequestBuilder implements AssertJ's
AssertProvider interface. which can be used with the following method of AssertJ's Assertions:
public static <T> T assertThat(final AssertProvider<T> component)
This means the above builder itself can be passed directly to assertThat(), which internally calls AssertProvider.assertThat() to produce the appropriate assertion object:
MvcTestResultAssert mvcTestResultAssert = assertThat(mockMvc.get()
.uri("/greeting"));
The result type
MvcTestResultAssert is an implementation of
org.assertj.core.api.Assert so it can be further used for assertions.
See also how to create a AssertJ's
custom assertion.
Example
package com.logicbig.example;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@Controller
@ResponseBody
public class GreetingController {
@GetMapping("/greeting")
public String greeting(
@RequestParam(name = "name",
defaultValue = "Guest") String name) {
return "Hello, " + name + "!";
}
}
Unit testing using AssertJ
For full Spring context support — where filters, interceptors, and other beans are active — use MockMvcTester.from(wac) with @WebAppConfiguration and @ContextConfiguration as shown in the example below:
package com.logicbig.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {GreetingController.class})
@WebAppConfiguration
public class GreetingControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvcTester mockMvc;
@BeforeEach
void setup() {
mockMvc = MockMvcTester.from(wac);
}
@Test
void defaultGreeting() {
assertThat(mockMvc.get().uri("/greeting"))
.hasStatusOk()
.bodyText().contains("Hello, Guest!");
System.out.println("Default greeting OK");
}
@Test
void namedGreeting() {
MvcTestResult result = mockMvc.get().uri("/greeting")
.param("name", "World")
.exchange();
assertThat(result).hasStatusOk();
assertThat(result).bodyText().isEqualTo("Hello, World!");
System.out.println("Named greeting OK");
}
}
Output$ mvn clean test -Dtest=GreetingControllerTest [INFO] Scanning for projects... [INFO] [INFO] -------< com.logicbig.example:spring-mvc-testing-using-assertj >-------- [INFO] Building spring-mvc-testing-using-assertj 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-mvc-testing-using-assertj --- [INFO] Deleting D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-mvc-testing-using-assertj --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\src\main\resources [INFO] [INFO] --- compiler:3.13.0:compile (default-compile) @ spring-mvc-testing-using-assertj --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 1 source file with javac [debug target 17] to target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-mvc-testing-using-assertj --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\src\test\resources [INFO] [INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ spring-mvc-testing-using-assertj --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 2 source files with javac [debug target 17] to target\test-classes [INFO] [INFO] --- surefire:3.3.1:test (default-test) @ spring-mvc-testing-using-assertj --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.GreetingControllerTest Default greeting OK Named greeting OK [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.851 s -- in com.logicbig.example.GreetingControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.835 s [INFO] Finished at: 2026-03-16T15:36:54+08:00 [INFO] ------------------------------------------------------------------------ Mar 16, 2026 3:36:54 PM org.springframework.mock.web.MockServletContext log INFO: Initializing Spring TestDispatcherServlet '' Mar 16, 2026 3:36:54 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Initializing Servlet '' Mar 16, 2026 3:36:54 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Completed initialization in 236 ms Mar 16, 2026 3:36:54 PM org.springframework.mock.web.MockServletContext log INFO: Initializing Spring TestDispatcherServlet '' Mar 16, 2026 3:36:54 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Initializing Servlet '' Mar 16, 2026 3:36:54 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Completed initialization in 4 ms
We can also test our controller directly without a Spring context by using MockMvcTester.of(), passing the controller instance directly. This approach is faster as it skips context initialization, but it does not apply any Spring filters, interceptors, or other web layer beans:
MockMvcTester mockMvc = MockMvcTester.of(new GreetingController());
We can also test our controller directly without spring context:
package com.logicbig.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.assertj.MvcTestResult;
import static org.assertj.core.api.Assertions.assertThat;
public class GreetingDirectControllerTest {
private MockMvcTester mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcTester.of(new GreetingController());
}
@Test
void defaultGreeting() {
assertThat(mockMvc.get().uri("/greeting"))
.hasStatusOk()
.bodyText().contains("Hello, Guest!");
System.out.println("Default greeting OK");
}
@Test
void namedGreeting() {
MvcTestResult result = mockMvc.get().uri("/greeting")
.param("name", "World")
.exchange();
assertThat(result).hasStatusOk();
assertThat(result).bodyText().isEqualTo("Hello, World!");
System.out.println("Named greeting OK");
}
}
Output$ mvn clean test -Dtest=GreetingDirectControllerTest [INFO] Scanning for projects... [INFO] [INFO] -------< com.logicbig.example:spring-mvc-testing-using-assertj >-------- [INFO] Building spring-mvc-testing-using-assertj 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-mvc-testing-using-assertj --- [INFO] Deleting D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-mvc-testing-using-assertj --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\src\main\resources [INFO] [INFO] --- compiler:3.13.0:compile (default-compile) @ spring-mvc-testing-using-assertj --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 1 source file with javac [debug target 17] to target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-mvc-testing-using-assertj --- [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc-testing\spring-mvc-testing-using-assertj\src\test\resources [INFO] [INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ spring-mvc-testing-using-assertj --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 2 source files with javac [debug target 17] to target\test-classes [INFO] [INFO] --- surefire:3.3.1:test (default-test) @ spring-mvc-testing-using-assertj --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.GreetingDirectControllerTest Default greeting OK Named greeting OK [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.400 s -- in com.logicbig.example.GreetingDirectControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.762 s [INFO] Finished at: 2026-03-16T15:38:22+08:00 [INFO] ------------------------------------------------------------------------ Mar 16, 2026 3:38:22 PM org.springframework.mock.web.MockServletContext log INFO: Initializing Spring TestDispatcherServlet '' Mar 16, 2026 3:38:22 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Initializing Servlet '' Mar 16, 2026 3:38:22 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Completed initialization in 2 ms Mar 16, 2026 3:38:22 PM org.springframework.mock.web.MockServletContext log INFO: Initializing Spring TestDispatcherServlet '' Mar 16, 2026 3:38:22 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Initializing Servlet '' Mar 16, 2026 3:38:22 PM org.springframework.web.servlet.FrameworkServlet initServletBean INFO: Completed initialization in 0 ms
Conclusion
The test output confirms that both the GET /greeting and GET /greeting?name=World requests return HTTP 200 with the expected response body text. By using MockMvcTester and AssertJ, we eliminate the dependency on Hamcrest matchers and write all assertions using the same fluent assertThat() style used throughout the rest of the JUnit 5 test suite — demonstrating how Spring Framework 6.2 unifies the controller testing experience with the broader AssertJ ecosystem.
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 6.2.0 - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- spring-test 7.0.6 (Spring TestContext Framework)
- JDK 25
- Maven 3.9.11
|