AAA-MockMvc is a Spring Boot testing framework that provides a fluent API for creating
clean,
maintainable, and strongly-typed MockMvc integration tests.
It follows the classic Arrange–Act–Assert (AAA) pattern, helping developers write expressive and
consistent tests for their REST controllers and services.
Instead of manually wiring MockMvc and handling JSON serialization, AAA-MockMvc guides you
through
each testing phase — Arrange, Act, and Assert — using a step-by-step, type-safe DSL.
This approach improves readability, reusability, and test reliability for
Spring Boot applications written in Java 17+.
Key benefits:
- Simplifies MockMvc integration testing with a fluent, intuitive DSL.
- Enforces a clear Arrange-Act-Assert structure for every test case.
- Minimizes boilerplate setup for
ObjectMapper, headers, and request/response handling. - Integrates seamlessly with existing Spring Boot test configurations and beans.
Whether you’re building REST APIs, testing controllers, or ensuring contract consistency, AAA-MockMvc makes your Spring Boot integration tests both cleaner and faster.
This is a quality-of-life release poured with a lot of care tests read cleaner and fail clearer.
- AAA blocks are now much simpler — shorter and easier to follow.
- The phases are clearly separated, making each test easier to scan.
- New steps let you group multiple AAA blocks in one test and put a clear name on error messages.
- Sharper snapshot behavior: act().perform() runs once; assertions and answers stay pure (no extra I/O).
- See full details in the Release Note
- 1. Installation
- 2. Getting Started
- 3. Creating a Test
- 4. Working with Steps
- 5. Using Custom Bean
- 6. Manuel Setup
To include AAA-MockMvc in the project, add the following dependency to the pom.xml.
The sources can also be downloaded directly to access the documentation of the classes.
<dependency>
<groupId>io.github.co-mmer</groupId>
<artifactId>aaa-mockmvc</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
AAA-MockMvc uses a typed fluent API that guides you through the classic AAA flow:
- Arrange – build the request (method, URL/URI, query, headers, body).
- Act – execute the request once and capture a snapshot (status, headers, body).
- Assert – verify the snapshot; no additional I/O is performed.
- Answer (optional) – read the same snapshot as data (string/bytes/object/list/set/map).
The Framework exposes only context-appropriate methods. For example, GET/DELETE/HEAD/OPTIONS
do not offer a request body, while POST/PUT/PATCH do.
For more details and edge cases (e.g., headers, content negotiation, error messages), see the JavaDoc of the arrange/act/assert/answer APIs.
To use AAA-MockMvc, the test class must first inherit from 'AAAMockMvcTestSupport'
AAAMockMvcTestSupport exposes the AAA entry
points (arrange(), act(), asserts(), answer() , step())
@SpringBootTest
class MyTest extends AAAMockMvcTestSupport {
}Build the request: choose the HTTP method and URL/URI, then (optionally) add path variables,
query parameters, headers, and a body. No network I/O happens in this phase—execution occurs
later in act().perform().
For details and edge cases, see the JavaDoc of the arrange APIs.
Examples
@SpringBootTest
class MyTest extends AAAMockMvcTestSupport {
@Test
void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {
arrange()
.post(BASE + CREATE_USER)
.query("lang", "de")
.body()
.json(new User("Napoleon"))
.headers()
.auth("token-123");
}
}Execute the arranged request once and capture a response snapshot (status, headers, body). No assertions are performed here; verification happens in the next phase.
For details and edge cases, see the JavaDoc of the act APIs.
Examples
@SpringBootTest
class MyTest extends AAAMockMvcTestSupport {
@Test
void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {
arrange()
.post(BASE + CREATE_USER)
.query("lang", "de")
.body()
.json(new User("Napoleon"))
.headers()
.auth("token-123");
act()
.perform();
}
}Verify the response snapshot captured in act().perform(). No additional I/O happens here.
What you can assert
- Status: exact codes or ranges.
- Headers: key presence , key–value pairs and exact multi-value matches.
- Content as string / bytes / object / collection / map (object/collection/map use the configured ObjectMapper)
For details and edge cases, see the JavaDoc of the asserts APIs.
Examples
@SpringBootTest
class MyTest extends AAAMockMvcTestSupport {
@Test
void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {
arrange()
.post(BASE + CREATE_USER)
.query("lang", "de")
.body()
.json(new User("Napoleon"))
.headers()
.auth("token-123");
act()
.perform();
asserts()
.status()
.isCreated()
.content()
.asClass(UserResponse.class)
.isNotNull()
.matchAll(userResponse -> userResponse.status().equals("pending"));
}
}Read data from the same snapshot captured in act().perform()—no additional I/O is
performed. Use this to drive follow-up steps (e.g., IDs, payloads, or full objects).
- Return as string / bytes
- Deserialize as object, list, set, or map (uses the configured ObjectMapper)
For details and edge cases, see the JavaDoc of the answer APIs.
Examples
@SpringBootTest
class MyTest extends AAAMockMvcTestSupport {
@Test
void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {
arrange()
.post(BASE + CREATE_USER)
.query("lang", "de")
.body()
.json(new User("Napoleon"))
.headers()
.auth("token-123");
act()
.perform();
asserts()
.status()
.isCreated()
.content()
.asClass(UserResponse.class)
.isNotNull()
.matchAll(userResponse -> userResponse.status().equals("pending"));
var userResponse = answer().asObject(UserResponse.class);
}
}Use steps to group multiple AAA blocks within a single test. A step gives your flow a name, isolates state, and makes failure messages easier to read (“Step 'Create user' …”).
When to use
- You call multiple endpoints in one test (e.g., create → update → verify).
- You need to pass data from one response to the next request.
Wrap a named, isolated AAA block in a step to improve readability and error messages.
This example shows multiple steps without using answer().
Examples
@SpringBootTest
class UserIT extends AAAMockMvcTestSupport {
@Test
void GIVEN_addTwiceUser_WHEN_loadUsers_THEN_containExpectedUsers() {
step(
"Add Napoleon", () -> {
arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Napoleon"));
act().perform();
asserts().status().isCreated();
});
step(
"Add Gandolf", () -> {
arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Gandolf"));
act().perform();
asserts().status().isCreated();
});
step(
"Napoleon and Gandolf are saved", () -> {
arrange().get(BASE + USERS);
act().perform();
asserts()
.content().asList(User.class)
.hasSize(2)
.matchAny(
user -> user.name().equals("Napoleon"),
user -> user.name().equals("Gandolf")
);
});
}
}A step can return a value. Inside the step, the value comes from the last call to answer() .as…(...). If you don’t call answer() at all, the step returns null. The result type is simply inferred from where you assign it.
Examples
@SpringBootTest
class UserIT extends AAAMockMvcTestSupport {
@Test
void GIVEN_two_users_WHEN_list_THEN_contains_both() {
step(
"Add Napoleon", () -> {
arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Napoleon"));
act().perform();
asserts().status().isCreated();
});
step(
"Add Gandolf", () -> {
arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Gandolf"));
act().perform();
asserts().status().isCreated();
});
List<User> users = step(
"Napoleon and Gandolf are saved", () -> {
arrange().get(BASE + USERS);
act().perform();
asserts()
.content().asList(User.class)
.hasSize(2)
.matchAny(
user -> user.name().equals("Napoleon"),
user -> user.name().equals("Gandolf"));
answer().asList(User.class);
});
}
}AAA-MockMvc automatically discovers and uses your existing Spring beans.
Discovery order
- If a MockMvc bean exists, it is used as-is; otherwise a default instance is built.
- If an ObjectMapper bean exists, it is used; otherwise a default mapper is created.
Provide a Spring bean and AAA-MockMvc will use it automatically.
@SpringBootTest
class BeanObjectMapperCustomIT extends AAAMockMvcTestSupport {
@TestConfiguration
static class ObjectMapperConfig {
@Bean
public ObjectMapper objectMapper() {
var mapper = new ObjectMapper();
var module = new SimpleModule();
module.addDeserializer(String.class, new UpperCaseStringDeserializer());
mapper.registerModule(module);
return mapper;
}
}
@Test
void GIVEN_customObjectMapper_THEN_Uppercase() {
arrange()
.get(BASE + GET_USER);
act()
.perform();
asserts()
.content()
.asClass(UserResponse.class)
.matchAll(userResponse -> userResponse.status().equals("PENDING"));
}
}Expose a preconfigured MockMvc bean — filters, interceptors, and default actions are respected.
@SpringBootTest
class BeanMockMvcCustomIT extends AAAMockMvcTestSupport {
@TestConfiguration
static class MockMvcConfig {
@Bean
public MockMvc mockMvc(WebApplicationContext wac) {
var filter = new OncePerRequestFilter() {
@Override
@SneakyThrows
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) {
res.addHeader("X-Custom-MockMvc", "active");
chain.doFilter(req, res);
}
};
return MockMvcBuilders.webAppContextSetup(wac)
.addFilters(filter)
.alwaysDo(print())
.build();
}
}
@Test
void GIVEN_customMockMvc_THEN_customHeader() {
arrange()
.get(BASE + GET_USER);
act()
.perform();
asserts()
.headers()
.containsEntry("X-Custom-MockMvc", "active");
}
}While the recommended entry point is AAAMockMvcTestSupport (it auto-wires everything and keeps
tests
lean), you can also use the framework without extending it.
Simply import the Spring configuration and inject AAAMockMvc yourself.
To use AAA-MockMvc without AAAMockMvcTestSupport, add @ExtendWith(AAAMockMvcExtension.class)
and @Import(AAAMockMvcConfig.class) to your test and autowire an AAAMockMvc field.
@ExtendWith(AAAMockMvcExtension.class)
@Import(AAAMockMvcConfig.class)
@SpringBootTest
class MyTest {
@Autowired
private AAAMockMvc aaa;
}For a deeper dive into AAA, see Chapter 3 – Creating a Test
@ExtendWith(AAAMockMvcExtension.class)
@Import({AAAMockMvcConfig.class})
@SpringBootTest
class UserIT {
@Autowired
private AAAMockMvc aaa;
@Test
void GIVEN_user_WHEN_create_THEN_status_is_created() {
aaa.arrange()
.post(BASE + CREATE_USER)
.body()
.json(new User("Napoleon"));
aaa.act()
.perform();
aaa.asserts()
.status()
.isCreated();
}
}For a deeper dive into steps, see Chapter 4 – Working with Steps
@ExtendWith(AAAMockMvcExtension.class)
@Import({AAAMockMvcConfig.class})
@SpringBootTest
class UserIT {
@Autowired
private AAAMockMvc aaa;
@Test
void GIVEN_two_users_WHEN_list_THEN_contains_both() {
aaa.step("Add Napoleon", () -> {
aaa.arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Napoleon"));
aaa.act().perform();
aaa.asserts().status().isCreated();
});
aaa.step("Add Gandalf", () -> {
aaa.arrange()
.post(BASE + CREATE_USER)
.body().json(new User("Gandalf"));
aaa.act().perform();
aaa.asserts().status().isCreated();
});
List<User> users = aaa.step("Napoleon and Gandolf are saved", () -> {
aaa.arrange().get(BASE + USERS);
aaa.act().perform();
aaa.asserts()
.content().asList(User.class)
.hasSize(2)
.matchAny(
u -> u.name().equals("Napoleon"),
u -> u.name().equals("Gandalf"));
aaa.answer().asList(User.class);
});
}
}
This project is licensed under the Apache License, Version 2.0. See LICENSE.txt for more
information.
