Close

Spring MVC - Unit Testing with AssertJ

[Last Updated: Mar 16, 2026]

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 Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 6.2.0 - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
      javax.servlet-api:4.x
      javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    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

Spring MVC - Unit Testing using AssertJ Select All Download
  • spring-mvc-testing-using-assertj
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example
                • GreetingControllerTest.java

    See Also

    Join