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
相关产品推荐
相关产品推荐

