Pact Provider测试中Mockito无法Mock服务层导致验证失败的问题求助
最近在给我的API做Pact测试,消费者侧已经顺利定义好契约并上传到Pact Broker了,但到了提供者这边验证契约的时候,测试一直失败——本来应该被Stub的服务层居然真的执行了真实逻辑,直接抛出了ResourceNotFoundException: Client client1 with app myapp not found这个自定义异常。我检查了Mockito的Stub代码,看起来没啥问题,但就是不生效,实在搞不懂哪里漏了,来求助各位大佬!
先贴一下我的测试代码:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ExtendWith(SpringExtension.class) @Provider("lockedOut_identityService") @PactBroker(url = "http://localhost:9292") @ExtendWith(MockitoExtension.class) public class AuthServiceProviderTest { @Mock private ClientService clientService; @Value("${local.server.port}") private int port; @BeforeEach void before(PactVerificationContext context) { context.setTarget(new HttpTestTarget("localhost", port, "/")); } @TestTemplate @ExtendWith(PactVerificationInvocationContextProvider.class) void pactVerificationTestTemplate(PactVerificationContext context) { if (context != null) { context.verifyInteraction(); } } @State("Get client by appName & clientId") public void getClient() { when(clientService.getClient(eq(null), any(String.class), any(String.class))) .thenReturn(ClientResponse.builder() .id(1L) .clientId("client1") .clientSecret("secret") .authMethod(null) .authGrantType(null) .redirectUri("redirectUri") .createdAt(LocalDateTime.now()) .build()); } }
对应的控制器代码:
@GetMapping("/get-client") public ResponseEntity<ClientResponse> getClient( @RequestHeader(value = "x-correlation-id", required = true) String correlationId, @RequestParam(value = "id", required = false) Long id, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "clientId", required = false) String clientId ) { if ((id == null && appName != null && clientId == null) || (id == null && appName == null)) { throw new InvalidRequestException(ErrorConstant.INVALID_REQUEST.getValue()); } return new ResponseEntity<>(clientService.getClient(id, appName, clientId), HttpStatus.OK); }
测试时触发的请求参数是:id: null,appname: "myapp",clientId: "client1",用的依赖是:implementation 'au.com.dius.pact.provider:junit5:4.6.3'
我感觉Mockito的Stub逻辑是对的,但为什么服务层还是会执行真实代码呢?是不是Pact这边还有什么额外的配置没做?
可能的问题及解决方案
我自己琢磨了一下,大概率是Mock对象没有被正确注入到Spring容器导致的:
替换
@Mock为@MockBean
你现在用的@Mock是Mockito的原生注解,它只会在当前测试类里创建一个Mock实例,但Spring容器完全不知道这个实例的存在——控制器里依赖注入的还是容器中原本的真实ClientServiceBean,所以请求过来时调用的是真实的服务层方法,自然会查数据库报错。
换成Spring Boot提供的@MockBean注解就不一样了,它会自动把这个Mock对象注册到Spring应用上下文中,替换掉原来的ClientServiceBean,这样控制器注入的就是Mock实例,你的Stub逻辑就能生效了。调整参数匹配(可选但建议)
你当前的Stub用的是eq(null), any(String.class), any(String.class),虽然能匹配到请求,但可以更精准一点,比如改成isNull(), eq("myapp"), eq("client1"),这样能确保只匹配测试场景中的特定请求,避免意外的Stub生效。
修改后的测试类核心部分:
// 去掉多余的@ExtendWith(MockitoExtension.class)也可以,@SpringBootTest已经包含相关支持 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Provider("lockedOut_identityService") @PactBroker(url = "http://localhost:9292") public class AuthServiceProviderTest { @MockBean // 这里改成@MockBean private ClientService clientService; // ...其他代码不变 @State("Get client by appName & clientId") public void getClient() { when(clientService.getClient(isNull(), eq("myapp"), eq("client1"))) .thenReturn(ClientResponse.builder() .id(1L) .clientId("client1") .clientSecret("secret") .authMethod(null) .authGrantType(null) .redirectUri("redirectUri") .createdAt(LocalDateTime.now()) .build()); } }
另外还要确认ClientService类本身是被Spring管理的(比如加了@Service注解),这样@MockBean才能找到对应的Bean进行替换。
备注:内容来源于stack exchange,提问作者Sy3d

