You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 11+JUnit5下保存方法的单元与集成测试(含数据库)咨询

单元测试 vs 集成测试:定义、区别及实战示例

核心概念与你的理解验证

单元测试

聚焦单个功能单元(如一个方法、一个类)的逻辑正确性,完全隔离所有外部依赖(数据库、其他服务、甚至其他协作类),用Mock替代所有外部交互,只验证当前单元的逻辑是否符合预期。

集成测试

验证多个组件/系统协同工作的正确性,比如Controller调用Service、Service调用Repository、Repository操作数据库的完整链路;或是跨服务的交互。你的理解完全正确:集成测试要使用真实的核心依赖(如真实数据库),不能Mock关键协作组件,目的是验证整个数据流的完整性和正确性。


针对你的代码的测试示例

一、单元测试示例

1. Controller层单元测试

用MockMvc模拟HTTP请求,Mock掉MessageService,仅验证Controller的请求映射、参数绑定、响应逻辑是否正常。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(MessageController.class)
public class MessageControllerUnitTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean // Mock Service,隔离外部依赖
    private MessageService messageService;

    @Test
    void saveMessage_ShouldReturnOkAndMatchingDto() throws Exception {
        // 构造测试DTO
        MessageDto testDto = MessageDto.builder()
                .id(1L)
                .title("测试标题")
                .name("张三")
                .mail("zhangsan@test.com")
                .message("测试内容")
                .build();

        // Mock Service行为:调用saveMessage时无返回值(仅验证Controller逻辑)
        doNothing().when(messageService).saveMessage(any(Message.class));

        // 发送POST请求并验证响应
        mockMvc.perform(post("/api/message/save")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"id\":1,\"title\":\"测试标题\",\"name\":\"张三\",\"mail\":\"zhangsan@test.com\",\"message\":\"测试内容\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.title").value("测试标题"));
    }
}

2. Service层单元测试

Mock掉MessageRepository,验证Service的DTO转实体逻辑、Repository调用逻辑是否正确。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class MessageServiceUnitTest {

    @Mock // Mock Repository
    private MessageRepository messageRepository;

    @InjectMocks // 注入Service,自动替换Mock的Repository
    private MessageService messageService;

    @Test
    void dtoToEntity_ShouldConvertAllFieldsCorrectly() {
        // 构造测试DTO
        MessageDto dto = MessageDto.builder()
                .id(2L)
                .title("转换测试")
                .name("李四")
                .mail("lisi@test.com")
                .message("转换内容")
                .build();

        // 执行转换逻辑
        Message entity = messageService.dtoToEntity(dto);

        // 验证转换结果
        assertEquals(dto.getId(), entity.getId());
        assertEquals(dto.getTitle(), entity.getTitle());
        assertEquals(dto.getName(), entity.getName());
        assertEquals(dto.getMail(), entity.getMail());
        assertEquals(dto.getMessage(), entity.getMessage());
    }

    @Test
    void saveMessage_ShouldReturnSavedEntity() {
        // 构造测试实体
        Message testEntity = Message.builder()
                .id(3L)
                .title("保存测试")
                .build();

        // Mock Repository的save方法,返回预设实体
        when(messageRepository.save(any(Message.class))).thenReturn(testEntity);

        // 执行保存逻辑
        Message savedEntity = messageService.saveMessage(testEntity);

        // 验证结果
        assertNotNull(savedEntity);
        assertEquals(3L, savedEntity.getId());
        assertEquals("保存测试", savedEntity.getTitle());
    }
}

二、集成测试示例

集成测试覆盖从Controller到数据库的完整链路,使用内存数据库(如H2)避免污染真实环境,不Mock核心组件,验证数据流的完整性。

1. 添加测试依赖(Maven)

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

2. 集成测试类代码

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MessageControllerIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private MessageRepository messageRepository; // 注入真实Repository,验证数据是否存入数据库

    @Test
    void saveMessage_ShouldPersistDataAndReturnOk() {
        // 构造测试DTO
        MessageDto testDto = MessageDto.builder()
                .title("集成测试标题")
                .name("王五")
                .mail("wangwu@test.com")
                .message("集成测试内容")
                .build();

        // 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<MessageDto> request = new HttpEntity<>(testDto, headers);

        // 发送POST请求
        var response = restTemplate.postForEntity("/api/message/save", request, MessageDto.class);

        // 验证响应状态和返回值
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody());
        assertEquals("集成测试标题", response.getBody().getTitle());

        // 验证数据库中是否真的存入数据
        Message savedEntity = messageRepository.findByMail("wangwu@test.com").orElse(null);
        assertNotNull(savedEntity);
        assertEquals("王五", savedEntity.getName());
        assertEquals("集成测试内容", savedEntity.getMessage());
    }
}

行业最佳实践

单元测试

  • 每个测试用例只验证一个逻辑点,避免复杂的多断言场景
  • 优先覆盖核心业务逻辑,不必追求100%代码覆盖率(如简单的getter/setter可跳过)
  • 用Mock完全隔离外部依赖,确保测试结果仅受当前单元逻辑影响
  • 测试用例命名要清晰,如dtoToEntity_ShouldConvertAllFieldsCorrectly,一眼可理解测试内容

集成测试

  • 使用内存数据库(H2、HSQLDB),避免依赖真实环境,提升测试速度
  • 测试前清理数据库或使用@Transactional注解自动回滚,避免测试数据互相干扰
  • 只验证核心链路的数据流正确性,边界情况交给单元测试
  • 减少断言数量,重点验证数据是否正确持久化、响应是否符合预期

内容的提问来源于stack exchange,提问作者helloApp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 22:45:38