Spring Boot中Cucumber测试Mock服务失败且WebApplicationContext注入异常
解决Cucumber测试中WebApplicationContext注入失败与Mock不生效的问题
你的问题主要来自Cucumber与Spring Boot整合的生命周期不匹配,以及Mock对象未被Spring容器接管这两个关键点,下面分步给出解决方案:
一、修复WebApplicationContext注入失败的问题
1. 调整Spring与Cucumber的整合配置
Cucumber有自己的上下文加载机制,不能直接在步骤类上用JUnit的@SpringBootTest注解,需要单独创建一个Cucumber配置类:
package steps; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.web.WebAppConfiguration; import io.cucumber.spring.CucumberContextConfiguration; @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @WebAppConfiguration public class CucumberSpringConfig { }
2. 修正步骤类的注解与生命周期方法
移除步骤类DeleteDesignationById上的@SpringBootTest、@ContextConfiguration、@WebAppConfiguration注解,同时把JUnit的@BeforeEach换成Cucumber的@Before注解(因为Cucumber场景的初始化要遵循它自己的生命周期):
// 替换导入的包 import cucumber.api.java.Before; // ... 其他代码 @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext) .apply(springSecurity(springSecurityFilterChain)) .build(); }
这样Cucumber会通过配置类正确加载Spring上下文,@Autowired的WebApplicationContext就能正常注入了。
二、让DesignationServicesImpl的Mock生效
你手动创建的Mock对象并没有被Spring容器接管,接口调用时还是会用真实的服务实现,需要改用Spring Boot的@MockBean注解来替换真实Bean:
1. 替换手动Mock的代码
移除手动创建Mock的行:
// 删掉这行 private DesignationServicesImpl designationService=Mockito.mock(DesignationServicesImpl.class);
改用@MockBean注入Mock对象:
import org.springframework.boot.test.mock.mockito.MockBean; // ... 其他代码 @MockBean private DesignationServicesImpl designationService;
2. 调整Mock行为的设置
现在你在步骤方法里设置的Mock行为会被Spring容器识别,接口调用时会使用这个Mock实例:
@When("^I send Delete Request for Designation with its Id$") public void i_send_Delete_Request_for_Designation_with_its_Id() throws Throwable { String uri = "/api/designation/1"; // 设置Mock的返回值 when(designationService.deleteDesignationById(1L)) .thenReturn("deleted"); // 直接用setUp里初始化好的mockMvc,无需重复创建 this.mvcResult = mockMvc.perform( MockMvcRequestBuilders.delete(uri) .header("Authorization", token)) .andReturn(); }
三、额外的代码优化
- 把
MvcResult改成类变量,方便在Then步骤里断言响应状态:
private MvcResult mvcResult; // ... 其他代码 @Then("^Valid http response of (\\d+) recieved$") public void valid_http_response_of_recieved(int expectedStatus) throws Throwable { assertEquals(expectedStatus, mvcResult.getResponse().getStatus()); }
- 移除
header_is_with_Jwt_token_for_Delete_Request方法里的冗余代码,因为请求头可以直接在perform方法中设置。
最终的步骤类代码示例
package steps; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import javax.servlet.Filter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import com.exampletest.subpkg.employee.serviceimpls.DesignationServicesImpl; import cucumber.api.java.Before; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class DeleteDesignationById { String URI; private MvcResult mvcResult; String token = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJNYW5pIiwic2VjcmV0IjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJleHAiOjE1NzQxOTIxNjYsImlhdCI6MTU3NDE3NDE2Nn0._cwqQ65_N9qL1cV79zaAsiMbR2Y0eMQFG642Evb90gs"; @MockBean private DesignationServicesImpl designationService; @Autowired private WebApplicationContext webAppContext; @Autowired private Filter springSecurityFilterChain; private MockMvc mockMvc; @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext) .apply(springSecurity(springSecurityFilterChain)) .build(); } @Given("^I set service endpoint for Delete Designation$") public void i_set_service_endpoint_for_Delete_Designation() throws Throwable { URI = "http://localhost:8080/api/designation/1"; } @When("^header is with Jwt token for Delete Request$") public void header_is_with_Jwt_token_for_Delete_Request() throws Throwable { // 无需单独处理,请求头在后续调用中直接设置 } @When("^I send Delete Request for Designation with its Id$") public void i_send_Delete_Request_for_Designation_with_its_Id() throws Throwable { String uri = "/api/designation/1"; when(designationService.deleteDesignationById(1L)) .thenReturn("deleted"); this.mvcResult = mockMvc.perform( MockMvcRequestBuilders.delete(uri) .header("Authorization", token)) .andReturn(); } @Then("^Valid http response of (\\d+) recieved$") public void valid_http_response_of_recieved(int expectedStatus) throws Throwable { assertEquals(expectedStatus, mvcResult.getResponse().getStatus()); } }
内容的提问来源于stack exchange,提问作者Nikhil Kumar
相关产品推荐
相关产品推荐

