{"id":37020486,"url":"https://github.com/co-mmer/aaa-mockmvc","last_synced_at":"2026-01-14T02:22:52.519Z","repository":{"id":258635328,"uuid":"868378254","full_name":"co-mmer/aaa-mockmvc","owner":"co-mmer","description":"This project provides a framework for structuring unit tests following the AAA (Arrange, Act, Assert) pattern with a focus on clarity and readability.","archived":false,"fork":false,"pushed_at":"2025-10-18T13:15:40.000Z","size":620,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-19T05:54:56.201Z","etag":null,"topics":["aaa","arrange-act-assert","integrationstests","java","mockmvc","mvc","quality","testing","testing-framework"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/co-mmer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-10-06T08:22:12.000Z","updated_at":"2025-10-18T11:13:08.000Z","dependencies_parsed_at":"2024-11-10T14:17:18.896Z","dependency_job_id":"d57bbded-bcb8-4089-bc55-2884458ab5e7","html_url":"https://github.com/co-mmer/aaa-mockmvc","commit_stats":null,"previous_names":["co-mmer/aaa-mockmvc"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/co-mmer/aaa-mockmvc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/co-mmer%2Faaa-mockmvc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/co-mmer%2Faaa-mockmvc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/co-mmer%2Faaa-mockmvc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/co-mmer%2Faaa-mockmvc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/co-mmer","download_url":"https://codeload.github.com/co-mmer/aaa-mockmvc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/co-mmer%2Faaa-mockmvc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aaa","arrange-act-assert","integrationstests","java","mockmvc","mvc","quality","testing","testing-framework"],"created_at":"2026-01-14T02:22:52.059Z","updated_at":"2026-01-14T02:22:52.509Z","avatar_url":"https://github.com/co-mmer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"images/aaa-mockmvc-icon.png\" alt=\"aaa-mockmvc-icon\" style=\"width: 300px;\" /\u003e\n  \u003ch1\u003eAAA-MockMvc\u003c/h1\u003e\n\u003c/div\u003e\n\n\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n[![SQALE Rating](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=co-mmer_aaa-mockmvc\u0026metric=code_smells)](https://sonarcloud.io/summary/new_code?id=co-mmer_aaa-mockmvc)\n![Java](https://img.shields.io/badge/Java-17-blueviolet)\n![Maven Central](https://img.shields.io/maven-central/v/io.github.co-mmer/aaa-mockmvc)\n\n## Overview\n\n**AAA-MockMvc** is a **Spring Boot testing framework** that provides a **fluent API** for creating\nclean,\nmaintainable, and strongly-typed **MockMvc integration tests**.  \nIt follows the classic **Arrange–Act–Assert (AAA)** pattern, helping developers write expressive and\nconsistent tests for their REST controllers and services.\n\nInstead of manually wiring `MockMvc` and handling JSON serialization, **AAA-MockMvc** guides you\nthrough\neach testing phase — **Arrange**, **Act**, and **Assert** — using a step-by-step, type-safe DSL.  \nThis approach improves **readability**, **reusability**, and **test reliability** for\n**Spring Boot applications** written in **Java 17+**.\n\nKey benefits:\n\n- Simplifies **MockMvc integration testing** with a fluent, intuitive DSL.\n- Enforces a clear **Arrange-Act-Assert** structure for every test case.\n- Minimizes boilerplate setup for `ObjectMapper`, headers, and request/response handling.\n- Integrates seamlessly with existing **Spring Boot** test configurations and beans.\n\nWhether you’re building REST APIs, testing controllers, or ensuring contract consistency,\n**AAA-MockMvc** makes your **Spring Boot integration tests** both **cleaner and faster**.\n\n### Example\n\n\u003cimg src=\"images/aaa-mockmvc-example.png\" alt=\"AAA-MockMvc example test using Arrange Act Assert pattern in Spring Boot\"/\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [News](#news)\n- [User Guide](#user-guide)\n- [License](#license)\n\n---\n\n## News\n\n### 🌿 Release v2.0.0\n\nThis is a quality-of-life release poured with a lot of care tests read cleaner and fail clearer.\n\n#### What’s new\n\n- AAA blocks are now much simpler — shorter and easier to follow.\n- The phases are clearly separated, making each test easier to scan.\n- New steps let you group multiple AAA blocks in one test and put a clear name on error messages.\n- Sharper snapshot behavior: act().perform() runs once; assertions and answers stay pure (no extra\n  I/O).\n- See full details in the [Release Note](./RELEASE.md#200)\n\n---\n\n## User Guide\n\n- [1. Installation](#1-installation)\n- [2. Getting Started](#2-getting-started)\n- [3. Creating a Test](#3-creating-a-test)\n    - [3.1. Phase Arrange](#31-phase-arrange)\n    - [3.2. Phase Act](#32-phase-act)\n    - [3.3. Phase Assert](#33-phase-assert)\n    - [3.4. Phase Answer](#34-phase-answer)\n- [4. Working with Steps ](#4-working-with-steps)\n    - [4.1. Using steps](#41-using-step)\n    - [4.2. Using steps with return-object](#42-using-step-with-answer)\n- [5. Using Custom Bean ](#5-using-custom-beans)\n    - [5.1. ObjectMapper](#51-objectmapper)\n    - [5.2. MockMvc](#52-mockmvc)\n- [6. Manuel Setup](#6-manuel-setup)\n    - [6.1. Getting Started](#61-getting-started)\n    - [6.2. Creating a Test](#62-creating-a-test)\n    - [6.3. Working with Steps](#63-using-steps)\n\n---\n\n## 1. Installation\n\nTo include AAA-MockMvc in the project, add the following dependency to the `pom.xml`.\nThe sources can also be downloaded directly to access the documentation of the classes.\n\n```xml\n\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.co-mmer\u003c/groupId\u003e\n  \u003cartifactId\u003eaaa-mockmvc\u003c/artifactId\u003e\n  \u003cversion\u003e2.0.0\u003c/version\u003e\n  \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n\n```\n\n---\n\n## 2. Getting Started\n\nAAA-MockMvc uses a **typed fluent API** that guides you through the classic AAA flow:\n\n- **Arrange** – build the request (method, URL/URI, query, headers, body).\n- **Act** – execute the request **once** and capture a **snapshot** (status, headers, body).\n- **Assert** – verify the snapshot; no additional I/O is performed.\n- **Answer** (optional) – read the same snapshot as data (string/bytes/object/list/set/map).\n\nThe Framework exposes only **context-appropriate methods**. For example, `GET/DELETE/HEAD/OPTIONS`\ndo **not** offer a request body, while `POST/PUT/PATCH` do.\n\n\u003e For more details and edge cases (e.g., headers, content negotiation, error messages),\n\u003e see the JavaDoc of the arrange/act/assert/answer APIs.\n\n---\n\n## 3. Creating a Test\n\nTo use AAA-MockMvc, the test class must first inherit from '**AAAMockMvcTestSupport**'\n\n`AAAMockMvcTestSupport` exposes the AAA entry\npoints (`arrange()`, `act()`, `asserts()`, `answer()` , `step()`)\n\n``` java\n\n@SpringBootTest\nclass MyTest extends AAAMockMvcTestSupport {\n\n}\n```\n\n### 3.1 Phase arrange\n\nBuild the request: choose the HTTP method and URL/URI, then (optionally) add path variables,\nquery parameters, headers, and a body. **No network I/O happens in this phase**—execution occurs\nlater in `act().perform()`.\n\n\u003e For details and edge cases, see the JavaDoc of the arrange APIs.\n\n**Examples**\n\n```java\n\n@SpringBootTest\nclass MyTest extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {\n\n    arrange()\n        .post(BASE + CREATE_USER)\n        .query(\"lang\", \"de\")\n        .body()\n        .json(new User(\"Napoleon\"))\n        .headers()\n        .auth(\"token-123\");\n  }\n}\n```\n\n### 3.2 Phase act\n\nExecute the arranged request **once** and capture a **response snapshot** (status, headers, body).\nNo assertions are performed here; verification happens in the next phase.\n\n\u003e For details and edge cases, see the JavaDoc of the act APIs.\n\n**Examples**\n\n``` java\n@SpringBootTest\nclass MyTest extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {\n    \n    arrange()\n        .post(BASE + CREATE_USER)\n        .query(\"lang\", \"de\")\n        .body()\n        .json(new User(\"Napoleon\"))\n        .headers()\n        .auth(\"token-123\");\n    \n    act()\n      .perform();\n    \n  }\n}\n```\n\n### 3.3 Phase assert\n\nVerify the response **snapshot** captured in `act().perform()`. No additional I/O happens here.\n\n**What you can assert**\n\n- **Status**: exact codes or ranges.\n- **Headers**: key presence , key–value pairs and exact multi-value matches.\n- **Content** as **string / bytes / object / collection / map**   *(object/collection/map use the\n  configured ObjectMapper)*\n\n\u003e For details and edge cases, see the JavaDoc of the asserts APIs.\n\n**Examples**\n\n```java\n\n@SpringBootTest\nclass MyTest extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {\n\n    arrange()\n        .post(BASE + CREATE_USER)\n        .query(\"lang\", \"de\")\n        .body()\n        .json(new User(\"Napoleon\"))\n        .headers()\n        .auth(\"token-123\");\n\n    act()\n        .perform();\n\n    asserts()\n        .status()\n        .isCreated()\n        .content()\n        .asClass(UserResponse.class)\n        .isNotNull()\n        .matchAll(userResponse -\u003e userResponse.status().equals(\"pending\"));\n  }\n}\n\n```\n\n### 3.4 Phase answer\n\nRead data from the **same snapshot** captured in `act().perform()`—**no additional I/O** is\nperformed. Use this to drive follow-up steps (e.g., IDs, payloads, or full objects).\n\n- Return as **string** / **bytes**\n- Deserialize as **object**, **list**, **set**, or **map** (uses the configured ObjectMapper)\n\n\u003e For details and edge cases, see the JavaDoc of the answer APIs.\n\n**Examples**\n\n```java\n\n@SpringBootTest\nclass MyTest extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_newUser_WHEN_createUser_THEN_pendingUserIsCreated() {\n\n    arrange()\n        .post(BASE + CREATE_USER)\n        .query(\"lang\", \"de\")\n        .body()\n        .json(new User(\"Napoleon\"))\n        .headers()\n        .auth(\"token-123\");\n\n    act()\n        .perform();\n\n    asserts()\n        .status()\n        .isCreated()\n        .content()\n        .asClass(UserResponse.class)\n        .isNotNull()\n        .matchAll(userResponse -\u003e userResponse.status().equals(\"pending\"));\n\n    var userResponse = answer().asObject(UserResponse.class);\n  }\n}\n\n```\n\n---\n\n## 4. Working with Steps\n\nUse **steps** to group multiple AAA blocks within a single test. A step gives your flow a **name**,\nisolates **state**, and makes **failure messages** easier to read (“Step 'Create user' …”).\n\n**When to use**\n\n- You call **multiple endpoints** in one test (e.g., create → update → verify).\n- You need to **pass data** from one response to the next request.\n\n### 4.1 Using Step\n\nWrap a named, isolated AAA block in a **step** to improve readability and error messages.\nThis example shows multiple steps **without** using `answer()`.\n\n**Examples**\n\n```java\n\n@SpringBootTest\nclass UserIT extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_addTwiceUser_WHEN_loadUsers_THEN_containExpectedUsers() {\n\n    step(\n        \"Add Napoleon\", () -\u003e {\n          arrange()\n              .post(BASE + CREATE_USER)\n              .body().json(new User(\"Napoleon\"));\n          act().perform();\n          asserts().status().isCreated();\n        });\n\n    step(\n        \"Add Gandolf\", () -\u003e {\n          arrange()\n              .post(BASE + CREATE_USER)\n              .body().json(new User(\"Gandolf\"));\n          act().perform();\n          asserts().status().isCreated();\n        });\n\n    step(\n        \"Napoleon and Gandolf are saved\", () -\u003e {\n          arrange().get(BASE + USERS);\n          act().perform();\n          asserts()\n              .content().asList(User.class)\n              .hasSize(2)\n              .matchAny(\n                  user -\u003e user.name().equals(\"Napoleon\"),\n                  user -\u003e user.name().equals(\"Gandolf\")\n              );\n        });\n  }\n}\n\n```\n\n### 4.2 Using Step with Answer\n\nA step can return a value. Inside the step, the value comes from the last call to answer()\n.as…(...). If you don’t call answer() at all, the step returns null.\nThe result type is simply inferred from where you assign it.\n\n**Examples**\n\n```java\n\n@SpringBootTest\nclass UserIT extends AAAMockMvcTestSupport {\n\n  @Test\n  void GIVEN_two_users_WHEN_list_THEN_contains_both() {\n    step(\n        \"Add Napoleon\", () -\u003e {\n          arrange()\n              .post(BASE + CREATE_USER)\n              .body().json(new User(\"Napoleon\"));\n          act().perform();\n          asserts().status().isCreated();\n        });\n\n    step(\n        \"Add Gandolf\", () -\u003e {\n          arrange()\n              .post(BASE + CREATE_USER)\n              .body().json(new User(\"Gandolf\"));\n          act().perform();\n          asserts().status().isCreated();\n        });\n\n    List\u003cUser\u003e users = step(\n        \"Napoleon and Gandolf are saved\", () -\u003e {\n          arrange().get(BASE + USERS);\n          act().perform();\n          asserts()\n              .content().asList(User.class)\n              .hasSize(2)\n              .matchAny(\n                  user -\u003e user.name().equals(\"Napoleon\"),\n                  user -\u003e user.name().equals(\"Gandolf\"));\n          answer().asList(User.class);\n        });\n  }\n}\n\n```\n\n---\n\n## 5. Using Custom Beans\n\nAAA-MockMvc automatically **discovers and uses** your existing Spring beans.\n\n**Discovery order**\n\n1. If a **MockMvc** bean exists, it is used as-is; otherwise a default instance is built.\n2. If an **ObjectMapper** bean exists, it is used; otherwise a default mapper is created.\n\n### 5.1 ObjectMapper\n\nProvide a Spring bean and AAA-MockMvc will use it automatically.\n\n```java\n\n@SpringBootTest\nclass BeanObjectMapperCustomIT extends AAAMockMvcTestSupport {\n\n  @TestConfiguration\n  static class ObjectMapperConfig {\n\n    @Bean\n    public ObjectMapper objectMapper() {\n      var mapper = new ObjectMapper();\n      var module = new SimpleModule();\n      module.addDeserializer(String.class, new UpperCaseStringDeserializer());\n      mapper.registerModule(module);\n      return mapper;\n    }\n  }\n\n  @Test\n  void GIVEN_customObjectMapper_THEN_Uppercase() {\n    arrange()\n        .get(BASE + GET_USER);\n\n    act()\n        .perform();\n\n    asserts()\n        .content()\n        .asClass(UserResponse.class)\n        .matchAll(userResponse -\u003e userResponse.status().equals(\"PENDING\"));\n  }\n}\n```\n\n### 5.2 MockMvc\n\nExpose a preconfigured MockMvc bean — filters, interceptors, and default actions are respected.\n\n```java\n\n@SpringBootTest\nclass BeanMockMvcCustomIT extends AAAMockMvcTestSupport {\n\n  @TestConfiguration\n  static class MockMvcConfig {\n\n    @Bean\n    public MockMvc mockMvc(WebApplicationContext wac) {\n      var filter = new OncePerRequestFilter() {\n        @Override\n        @SneakyThrows\n        protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,\n            FilterChain chain) {\n          res.addHeader(\"X-Custom-MockMvc\", \"active\");\n          chain.doFilter(req, res);\n        }\n      };\n\n      return MockMvcBuilders.webAppContextSetup(wac)\n          .addFilters(filter)\n          .alwaysDo(print())\n          .build();\n    }\n  }\n\n  @Test\n  void GIVEN_customMockMvc_THEN_customHeader() {\n    arrange()\n        .get(BASE + GET_USER);\n\n    act()\n        .perform();\n\n    asserts()\n        .headers()\n        .containsEntry(\"X-Custom-MockMvc\", \"active\");\n  }\n}\n```\n\n---\n\n## 6. Manuel Setup\n\nWhile the recommended entry point is `AAAMockMvcTestSupport` (it auto-wires everything and keeps\ntests\nlean), you can also use the framework without extending it.\nSimply import the Spring configuration and inject `AAAMockMvc` yourself.\n\n### 6.1 Getting Started\n\nTo use AAA-MockMvc without `AAAMockMvcTestSupport`, add `@ExtendWith(AAAMockMvcExtension.class)`\nand `@Import(AAAMockMvcConfig.class)` to your test and autowire an `AAAMockMvc` field.\n\n``` java\n\n@ExtendWith(AAAMockMvcExtension.class)\n@Import(AAAMockMvcConfig.class)\n@SpringBootTest\nclass MyTest {\n\n  @Autowired\n  private AAAMockMvc aaa;\n\n}\n```\n\n### 6.2 Creating a Test\n\nFor a deeper dive into AAA, see Chapter 3 – [Creating a Test](#3-creating-a-test)\n\n``` java\n\n@ExtendWith(AAAMockMvcExtension.class)\n@Import({AAAMockMvcConfig.class})\n@SpringBootTest\nclass UserIT {\n\n  @Autowired\n  private AAAMockMvc aaa;\n\n  @Test\n  void GIVEN_user_WHEN_create_THEN_status_is_created() {\n    aaa.arrange()\n        .post(BASE + CREATE_USER)\n        .body()\n        .json(new User(\"Napoleon\"));\n\n    aaa.act()\n        .perform();\n\n    aaa.asserts()\n        .status()\n        .isCreated();\n  }\n}\n\n```\n\n### 6.3 Using steps\n\nFor a deeper dive into steps, see Chapter 4 – [Working with Steps](#4-working-with-steps)\n\n``` java\n\n@ExtendWith(AAAMockMvcExtension.class)\n@Import({AAAMockMvcConfig.class})\n@SpringBootTest\nclass UserIT {\n\n  @Autowired\n  private AAAMockMvc aaa;\n\n  @Test\n  void GIVEN_two_users_WHEN_list_THEN_contains_both() {\n    aaa.step(\"Add Napoleon\", () -\u003e {\n      aaa.arrange()\n          .post(BASE + CREATE_USER)\n          .body().json(new User(\"Napoleon\"));\n      aaa.act().perform();\n      aaa.asserts().status().isCreated();\n    });\n\n    aaa.step(\"Add Gandalf\", () -\u003e {\n      aaa.arrange()\n          .post(BASE + CREATE_USER)\n          .body().json(new User(\"Gandalf\"));\n      aaa.act().perform();\n      aaa.asserts().status().isCreated();\n    });\n\n    List\u003cUser\u003e users = aaa.step(\"Napoleon and Gandolf are saved\", () -\u003e {\n      aaa.arrange().get(BASE + USERS);\n      aaa.act().perform();\n      aaa.asserts()\n          .content().asList(User.class)\n          .hasSize(2)\n          .matchAny(\n              u -\u003e u.name().equals(\"Napoleon\"),\n              u -\u003e u.name().equals(\"Gandalf\"));\n      aaa.answer().asList(User.class); \n    });\n  }\n}\n\n\n```\n\n---\n\n## License\n\nThis project is licensed under the Apache License, Version 2.0. See `LICENSE.txt` for more\ninformation.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fco-mmer%2Faaa-mockmvc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fco-mmer%2Faaa-mockmvc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fco-mmer%2Faaa-mockmvc/lists"}