SpringBoot Integration Testing: Using @SpringBootTest with MockMvc

SpringBoot integration tests load the full application context to verify end‑to‑end behavior, while unit tests only cover isolated methods; this guide explains @SpringBootTest fundamentals, configuration options, MockMvc usage, test structure, common pitfalls, and best practices for reliable full‑stack testing.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
SpringBoot Integration Testing: Using @SpringBootTest with MockMvc

Why Integration Tests Matter

Unit tests that target a single service method can miss problems that appear only when the full request chain is exercised, such as controller parameter validation, interceptor execution, mapper return values, and global exception handling. Integration tests exercise the entire chain to catch these issues.

Integration Test vs Unit Test

Key differences:

Test Scope : Unit tests target a single class or method; integration tests cover the full chain (Controller → Service → Mapper → database or external API).

Spring Context : Unit tests do not load the context and rely on mocks; integration tests load the complete Spring context with real bean injection.

Core Tools : JUnit & Mockito for unit tests; @SpringBootTest, MockMvc, and optionally TestContainers for integration tests.

Speed : Unit tests run in milliseconds; integration tests take seconds because the context is started.

Applicable Scenarios : Unit tests verify isolated logic; integration tests validate full‑link behavior, configuration, and dependency interactions.

Environment Preparation

SpringBoot 2.2+ already includes spring-boot-starter-test (JUnit 5, MockMvc, Mockito). For older projects add it manually and keep the version aligned with the SpringBoot version. Optional dependencies for database or third‑party HTTP calls can be added as shown.

<!-- SpringBoot test core dependency (includes JUnit 5, MockMvc, Mockito) -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

<!-- Optional MySQL for DB tests -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>test</scope>
</dependency>

<!-- Optional OkHttp for external‑API simulation -->
<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>4.11.0</version>
  <scope>test</scope>
</dependency>

A recommended test package layout mirrors the main source tree:

com.example.demo
├── DemoApplication.java          // main class
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
├── mapper
│   └── UserMapper.java
└── src/test/java
    └── com.example.demo
        ├── controller
        │   └── UserControllerTest.java
        ├── service
        │   └── UserServiceTest.java
        └── DemoApplicationTests.java   // global entry point

@SpringBootTest Deep Dive

@SpringBootTest

is a context launcher. It scans for the class annotated with @SpringBootApplication, loads all configuration files (application.yml/properties, @Configuration classes, Bean definitions), and creates a complete Spring application context so that tests can inject real beans such as Service, Mapper, and Controller.

By default it does not start an embedded server unless the webEnvironment attribute is set.

Key Attributes

classes : Explicitly specify the startup class when multiple @SpringBootApplication classes exist. Example:

@SpringBootTest(classes = DemoApplication.class)
public class UserControllerTest {
    // test code
}

webEnvironment : Controls the web layer. WebEnvironment.MOCK (default) – mock servlet environment, suitable for MockMvc. WebEnvironment.RANDOM_PORT – starts a real server on a random port, useful for testing interceptors, CORS, etc. WebEnvironment.DEFINED_PORT – starts a real server on the port defined in configuration. WebEnvironment.NONE – no web environment, for pure service or mapper tests.

properties : Override configuration values for the test without touching application.yml. Example:

@SpringBootTest(properties = {
    "spring.datasource.url=jdbc:mysql://localhost:3306/test_db",
    "logging.level.com.example.demo=DEBUG"
})
public class ConfigOverrideTest {
    // test code
}

Basic Integration Example (Service Layer)

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class UserServiceIntegrationTest {
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testGetUserById() {
        // 1. Insert test data
        User testUser = new User(null, "测试用户", 25, "13800138000");
        userMapper.insert(testUser);
        Long userId = testUser.getId();
        // 2. Call service
        User resultUser = userService.getUserById(userId);
        // 3. Verify
        assertNotNull(resultUser);
        assertEquals("测试用户", resultUser.getUsername());
        assertEquals(25, resultUser.getAge());
        // 4. Clean up
        userMapper.deleteById(userId);
    }
}

No mocks are used; the test hits the real database (usually an in‑memory DB or a TestContainers instance).

MockMvc Advanced Usage

MockMvc simulates HTTP requests without starting a real server. It can test all HTTP verbs and verify status, headers, cookies, and JSON bodies.

Two Initialization Styles

Auto‑configuration (recommended): add @AutoConfigureMockMvc alongside @SpringBootTest and inject MockMvc directly.

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    // test methods …
}

Manual construction : useful when custom interceptors, filters, or multipart handling are needed.

@SpringBootTest
public class CustomMockMvcTest {
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext wac;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac)
            .addFileUpload(new MockMultipartFile("file", "test.txt", "text/plain", "test content".getBytes()))
            .setControllerAdvice(new GlobalExceptionHandler())
            .build();
    }
    // test methods …
}

Core Workflow

perform() : start a request, e.g. mockMvc.perform(get("/user/1")) or mockMvc.perform(post("/user/add")).

Configure request : add parameters, body, headers, cookies, etc.

// GET with path variable, query param, header, cookie
mockMvc.perform(get("/user/{id}", 1)
    .param("type", "1")
    .header("token", "test-token-123")
    .cookie(new Cookie("userId", "123")));

// POST JSON body
mockMvc.perform(post("/user/add")
    .contentType(APPLICATION_JSON)
    .accept(APPLICATION_JSON)
    .content("{\"username\":\"测试用户\",\"age\":25}"));

// POST form data
mockMvc.perform(post("/user/login")
    .contentType(APPLICATION_FORM_URLENCODED)
    .param("username", "admin")
    .param("password", "123456"));

// File upload
mockMvc.perform(multipart("/user/upload")
    .file(new MockMultipartFile("file", "test.jpg", "image/jpeg", "test image content".getBytes()))
    .param("desc", "测试图片"));

andExpect() : verify response status, headers, cookies, JSON fields, etc.

mockMvc.perform(get("/user/1"))
    .andExpect(status().isOk())
    .andExpect(header().string("Content-Type", "application/json"))
    .andExpect(cookie().value("userId", "123"))
    .andExpect(jsonPath("$.id").value(1))
    .andExpect(jsonPath("$.username").value("测试用户"))
    .andExpect(jsonPath("$.age").isNumber());

Additional helpers: andDo(print()) prints request/response details; andReturn() gives access to the raw MvcResult for custom assertions.

Common Scenarios (90% Coverage)

GET with path & query parameters – verify pagination fields.

@Test
public void testUserList() throws Exception {
    mockMvc.perform(get("/user/list")
        .param("pageNum", "1")
        .param("pageSize", "10")
        .header("token", "test-token"))
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.code").value(200))
        .andExpect(jsonPath("$.message").value("success"))
        .andExpect(jsonPath("$.data.list").isArray())
        .andExpect(jsonPath("$.data.pageNum").value(1))
        .andExpect(jsonPath("$.data.pageSize").value(10));
}

POST JSON with success and validation error

@Test
public void testAddUser_Success() throws Exception {
    User user = new User(null, "新用户", 22, "13800138000");
    String json = new ObjectMapper().writeValueAsString(user);
    mockMvc.perform(post("/user/add")
        .contentType(APPLICATION_JSON)
        .content(json))
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.code").value(200))
        .andExpect(jsonPath("$.message").value("添加成功"));
}

@Test
public void testAddUser_Error_UsernameNull() throws Exception {
    User user = new User(null, "", 22, "13800138000");
    String json = new ObjectMapper().writeValueAsString(user);
    mockMvc.perform(post("/user/add")
        .contentType(APPLICATION_JSON)
        .content(json))
        .andDo(print())
        .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.code").value(400))
        .andExpect(jsonPath("$.message").contains("username 不能为空"));
}

Interceptor / token validation

@Test
public void testWithoutToken() throws Exception {
    mockMvc.perform(get("/user/1"))
        .andDo(print())
        .andExpect(status().isUnauthorized())
        .andExpect(jsonPath("$.code").value(401))
        .andExpect(jsonPath("$.message").value("请先登录"));
}

@Test
public void testInvalidToken() throws Exception {
    mockMvc.perform(get("/user/1").header("token", "invalid-token"))
        .andDo(print())
        .andExpect(status().isUnauthorized())
        .andExpect(jsonPath("$.message").value("token 无效"));
}

File upload

@Test
public void testFileUpload() throws Exception {
    MockMultipartFile file = new MockMultipartFile(
        "file", "test.jpg", "image/jpeg", "test image content".getBytes());
    mockMvc.perform(multipart("/user/upload").file(file).param("desc", "测试图片"))
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.code").value(200))
        .andExpect(jsonPath("$.data.filename").value("test.jpg"));
}

Combining @SpringBootTest and MockMvc for Full‑Link Tests

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@AutoConfigureMockMvc
public class UserFullLinkIntegrationTest {
    @Autowired private MockMvc mockMvc;
    @Autowired private UserService userService;
    @Autowired private UserMapper userMapper;
    @Autowired private ObjectMapper objectMapper;

    /** Full‑chain test: HTTP → Service → Mapper → DB */
    @Test
    public void testUserFullLink() throws Exception {
        // 1. Insert test record
        User testUser = new User(null, "全链路测试用户", 30, "13900139000");
        userMapper.insert(testUser);
        Long userId = testUser.getId();
        // 2. MockMvc request
        mockMvc.perform(get("/user/{id}", userId)
            .header("token", "test-token-123"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(userId))
            .andExpect(jsonPath("$.username").value("全链路测试用户"))
            .andExpect(jsonPath("$.age").value(30));
        // 3. Verify service bean works
        User serviceUser = userService.getUserById(userId);
        assertNotNull(serviceUser);
        assertEquals("全链路测试用户", serviceUser.getUsername());
        // 4. Clean up
        userMapper.deleteById(userId);
    }
}

Test Data Isolation

Adding @Transactional to a test method rolls back all DB changes after the method finishes, keeping the database clean.

@Test
@Transactional
public void testWithTransaction() throws Exception {
    User testUser = new User(null, "事务测试用户", 28, "13700137000");
    userMapper.insert(testUser);
    Long userId = testUser.getId();
    mockMvc.perform(get("/user/{id}", userId))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.username").value("事务测试用户"));
    // No explicit delete needed – transaction rolls back
}
@Transactional

only rolls back database operations; external calls must be mocked separately.

10 Common Pitfalls and Solutions

@SpringBootTest starts slowly – load only needed beans via a custom @TestConfiguration.

MockMvc 404 errors – check URL prefixes, ensure the controller package is scanned, and add @AutoConfigureMockMvc.

Bean injection failures – verify that the startup class is found and beans are annotated with @Service, @Controller, or @Repository.

Test data contaminates the DB – use @Transactional or TestContainers for an isolated temporary database.

File upload not simulated – use multipart() or build MockMvc manually with file‑upload support.

jsonPath validation errors – ensure the response is JSON and the path expression matches the actual structure.

@LocalServerPort injection fails – only works when webEnvironment is RANDOM_PORT or DEFINED_PORT.

Test execution order appears random – JUnit 5 runs tests in random order by default; use @Order if a specific sequence is required.

Unstable tests that depend on third‑party services – mock those services with Mockito or WireMock.

Mixing unit and integration tests in the same class – keep them separate (e.g., XxxServiceTest for unit tests, XxxControllerIntegrationTest for integration tests).

Making Integration Tests More Efficient

Static Imports

Import MockMvc request builders, result matchers, and handlers statically to avoid fully‑qualified class names.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;

mockMvc.perform(get("/user/1")).andDo(print()).andExpect(status().isOk());

Base Test Class

Extract common fields ( MockMvc, ObjectMapper) and utility methods into an abstract base class.

@SpringBootTest
@AutoConfigureMockMvc
public abstract class BaseIntegrationTest {
    @Autowired protected MockMvc mockMvc;
    @Autowired protected ObjectMapper objectMapper;
    protected String toJson(Object obj) throws JsonProcessingException {
        return objectMapper.writeValueAsString(obj);
    }
}

public class UserControllerTest extends BaseIntegrationTest {
    @Test
    public void testUser() throws Exception {
        User user = new User(null, "测试", 25);
        mockMvc.perform(post("/user/add")
            .contentType(APPLICATION_JSON)
            .content(toJson(user)))
            .andExpect(status().isOk());
    }
}

TestContainers for Temporary Databases

When local DB versions differ, spin up a disposable MySQL/PostgreSQL container for the test lifecycle. Add the TestContainers dependency and configure a @Container field; the container starts before tests and stops afterwards, guaranteeing environment consistency.

Conclusion

SpringBoot integration testing hinges on loading the full application context with @SpringBootTest and simulating HTTP calls via MockMvc. Together they provide end‑to‑end verification of Controller → Service → Mapper → database flows, catch configuration or interceptor issues early, and increase confidence before deployment. By following the patterns, handling common pitfalls, and applying the efficiency tips above, developers can write maintainable, fast, and reliable integration tests.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

spring-bootintegration-testingMockMvcJUnit5testcontainers
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.