Spring Boot Controller 单元测试撰写
文章目录
- 引言
- 标准用法
- 必需依赖项
- 核心注解说明
- 代码示例
- 当涉及静态方法时的测试策略
- 必需依赖项
- 核心注解说明
- 代码示例
引言
之前在编写 Controller 层的单元测试时,我一直使用 @SpringBootTest 注解,但它会加载整个 Spring 应用上下文,资源开销大,我更倾向于采用更轻量级的测试策略,专注于 Web 层的功能验证。
在最近的单元测试撰写中,我采用了新的方法,并在此归纳记录:
标准用法
必需依赖项
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><!-- 版本可根据项目实际情况调整,需要和spring-boot版本适应--><scope>test</scope>
</dependency>
<!-- 如果不报错,一般不需要引入 -->
<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId><version>1.8.2</version><scope>test</scope>
</dependency>
<!-- 仅在需兼容 JUnit 4/3 时使用 -->
<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.8.2</version><scope>test</scope>
</dependency>
核心注解说明
- @WebMvcTest(controllers = YourController.class)
Spring Boot 提供的切片测试注解,专门用于测试 MVC 控制器层。它只加载 Spring MVC 相关组件(如 @Controller、@RestController、@ControllerAdvice 等),不会加载 Service、Repository 等其他 Bean。
- @ContextConfiguration(classes = YourController.class)
明确指定测试所需加载的配置类或组件,避免因自动注入无关 Bean 而导致测试失败。
代码示例
@WebMvcTest(controllers = YourController.class)
@ContextConfiguration(classes = YourController.class)
class YourControllerTest {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate YourService yourService;@Autowiredprivate ObjectMapper objectMapper;@Testvoid getSomething() throws Exception {// 模拟service层方法调用when(yourService.getSomething(anyString())).thenReturn(R.ok());// 模拟请求参数HashMap<String, Object> requestBody = new HashMap<>();requestBody.put("testParam", "test");// 开始模拟mockMvc.perform(MockMvcRequestBuilders.post("/xx/getSomething").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestBody))).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$.code").value(R.ok().getCode())).andExpect(jsonPath("$.data").isEmpty());}
}
当涉及静态方法时的测试策略
在极端情况下,如果 Controller 中存在通过工厂类静态方法构造的依赖(而不是通过 Spring 注入),该如何进行测试?
必需依赖项
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><!-- 版本自选,需要和spring-boot版本适应--><exclusions><exclusion><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId></exclusion></exclusions><scope>test</scope>
</dependency><!--不报错就没必要加它-->
<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId><version>1.8.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.8.2</version><scope>test</scope>
</dependency>
<!--旧版本 mockito-core 不支持静态方法 mock-->
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.2.0</version><scope>test</scope>
</dependency>
核心注解说明
- @ExtendWith(MockitoExtension.class)
启用 Mockito 的注解功能,包括 @Mock、@InjectMocks、@Spy 等支持。
代码示例
@ExtendWith(MockitoExtension.class)
class YourControllerTest {private ObjectMapper objectMapper = new ObjectMapper();@Testvoid getSomething() throws Exception {try(MockedStatic<XxxFactory> mocked = mockStatic(XxxFactory.class)){//用来替换的对象YourInstance instance = mock(YourInstance.class);//静态替换mocked.when(XxxFactory::getYourInstance).thenReturn(instance);//模拟方法调用when(instance.getSomething(anyString())).thenReturn(R.ok());// 模拟请求参数HashMap<String, Object> requestBody = new HashMap<>();requestBody.put("testParam", "test");// 开始模拟mockMvc.perform(MockMvcRequestBuilders.post("/xx/getSomething").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestBody))).andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$.code").value(R.ok().getCode())).andExpect(jsonPath("$.data").isEmpty());}}}