You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pact Provider测试中Mockito无法Mock服务层导致验证失败的问题求助

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容器导致的:

  1. 替换@Mock为@MockBean
    你现在用的@Mock是Mockito的原生注解,它只会在当前测试类里创建一个Mock实例,但Spring容器完全不知道这个实例的存在——控制器里依赖注入的还是容器中原本的真实ClientService Bean,所以请求过来时调用的是真实的服务层方法,自然会查数据库报错。
    换成Spring Boot提供的@MockBean注解就不一样了,它会自动把这个Mock对象注册到Spring应用上下文中,替换掉原来的ClientService Bean,这样控制器注入的就是Mock实例,你的Stub逻辑就能生效了。

  2. 调整参数匹配(可选但建议)
    你当前的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 11:08:00