Spring测试预期400实际返回404:控制器端点未找到排查
问题背景
编写了一个用于创建学生会议的Spring控制器,POST接口路径为/student/meeting,要求传入@RequestBody参数,预期无参数时返回400 Bad Request,但对应的集成测试运行后实际返回404 Not Found。
控制器代码
@Operation(summary = "Create a student meeting for agency ") @ApiResponses({ @ApiResponse(responseCode = "200", content = { @Content(schema = @Schema(implementation = MeetingResponse.class), mediaType = "application/json") }), @ApiResponse(responseCode = "400", content = { @Content(schema = @Schema(implementation = ErrorResponse.class), mediaType = "application/json") }), @ApiResponse(responseCode = "401", content = { @Content(schema = @Schema(implementation = ErrorResponse.class), mediaType = "application/json") }), }) @PostMapping(path = "/student/meeting") public MeetingJoinToken setupMeeting(@RequestBody StudentMeetingRequest studentMeetingRequest){ SetupMeetingRequest setupMeetingRequest = validateAndCreateSetupMeetingRequest(studentMeetingRequest, ParticipantRole.ATTENDEE); return studentService.setupMeeting(setupMeetingRequest); }
测试代码
@RunWith(SpringRunner.class) @SpringBootTest(classes = ThApplication.class) @ComponentScan(basePackages = "com.agency") @AutoConfigureMockMvc public class ControllerIntTest { private final String patientMeeting ="/student/meeting"; @Autowired private MockMvc mockMvc; @MockBean IamTokenManagerRepository iamTokenManagerRepository; @MockBean ConfigmapFilesDownloader configmapFilesDownloader; @BeforeClass public static void setupClass() { AthenaLogInitializer.getInstance().initialize(); } @Test public void th_joinSession_BadRequest() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/student/meeting")) .andExpect(MockMvcResultMatchers.status().isBadRequest()); } }
测试结果
Expected :400 Actual :404
依赖配置
testImplementation 'org.mockito:mockito-inline' testImplementation 'org.mockito:mockito-core' testImplementation 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure' testImplementation 'junit:junit' testImplementation 'org.springframework:spring-test' sourceSets { integrationTest { java { compileClasspath += main.output + test.output runtimeClasspath += main.output + test.output } } } configurations { integrationTestImplementation.extendsFrom(testImplementation) integrationTestRuntimeOnly.extendsFrom(testRuntimeOnly) }
可能的原因及解决方案
控制器未被Spring容器扫描加载
测试类添加了@ComponentScan(basePackages = "com.agency"),但需确认控制器类所在包是否属于com.agency或其子包。若控制器包不在扫描范围内,Spring无法识别该接口,导致请求返回404。
解决:检查控制器类的包路径,确保被@ComponentScan覆盖;若控制器在外部包,调整basePackages参数包含对应包路径。启动类未扫描到控制器
@SpringBootTest(classes = ThApplication.class)指定了启动类,但ThApplication的@SpringBootApplication默认仅扫描自身所在包及子包。如果控制器包与启动类包无父子关系,且启动类未额外配置@ComponentScan,控制器将无法被加载。
解决:在ThApplication上添加@ComponentScan,明确指定控制器所在包;或者调整控制器包结构,使其处于启动类包的子包下。请求未包含应用上下文路径
若应用配置了server.servlet.context-path(例如/api),测试中直接请求/student/meeting会因路径不完整导致404。
解决:检查application.properties或application.yml中的上下文路径配置,测试请求时拼接完整路径(如/api/student/meeting);或通过@Value("${server.servlet.context-path:}")注入上下文路径,动态拼接请求地址。无请求体导致接口匹配失败
Spring MVC中,要求@RequestBody的接口默认需要请求体存在。当测试中完全不发送请求体时,Spring可能认为该请求不匹配目标接口,进而返回404。此时需明确发送空请求体触发参数校验失败的400。
解决:修改测试代码,发送空JSON请求体:mockMvc.perform(MockMvcRequestBuilders.post("/student/meeting") .contentType(MediaType.APPLICATION_JSON) .content("{}")) .andExpect(MockMvcResultMatchers.status().isBadRequest());JUnit版本冲突
测试类使用了JUnit 4的@RunWith(SpringRunner.class),但依赖中同时包含JUnit 5的junit-jupiter-engine,可能导致测试运行环境混乱,影响Spring上下文的正确加载。
解决:统一JUnit版本:- 若用JUnit 4:移除
junit-jupiter-engine和mockito-junit-jupiter依赖,保留junit:junit和@RunWith注解。 - 若用JUnit 5:替换
@RunWith(SpringRunner.class)为@ExtendWith(SpringExtension.class)(或直接省略,@SpringBootTest已包含该支持),移除junit:junit依赖。
- 若用JUnit 4:移除
集成测试SourceSet配置不匹配
配置了单独的integrationTestsourceSet,但如果测试类放在默认的src/test/java下而非src/integrationTest/java,测试可能未使用正确的配置,导致Spring上下文加载异常。
解决:将测试类移到src/integrationTest/java目录下,与sourceSet配置匹配;若不需要单独的集成测试环境,可移除sourceSets和configurations中的相关配置,直接使用默认测试环境。
内容的提问来源于stack exchange,提问作者rasilvap

