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

Java中Mock SOAP调用返回空响应的问题排查与解决

解决SOAP调用Mock时请求不匹配的问题

我之前也碰到过类似的情况——SOAPMessage的对象匹配坑真的挺烦人的,毕竟默认的equals根本不会按XML内容去比较。咱们一步步来排查和解决:

第一步:先搞清楚请求到底哪里不一样

你说改了时间戳还是不行,那得先精准定位差异点。咱们把业务代码里生成的payload和测试里的testPayload都转成字符串,直接对比内容:

先写个工具方法把SOAPMessage转成字符串:

private String soapMessageToString(SOAPMessage msg) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    msg.writeTo(baos);
    return baos.toString();
}

然后在业务代码里临时加日志(或者调试时打印):

SOAPMessage payload = getAddressRequestPayload();
System.out.println("业务请求XML:" + soapMessageToString(payload)); // 临时打印
SOAPMessage responseMessage = soapConnection.call(payload, settings.addressUrl());

测试代码里也打印测试用的请求:

SOAPMessage testPayload = abc.getAddressRequestPayload();
System.out.println("测试请求XML:" + soapMessageToString(testPayload)); // 临时打印
SOAPMessage testResponse = getMockResponse();

对比这两个字符串,你就能找到差异了——可能是这些地方:

  • 时间戳的毫秒级差异(比如你改了时间但没精确到毫秒)
  • 自动生成的请求ID、UUID之类的随机值
  • XML里的空格、换行符差异(比如业务代码生成的XML有缩进,测试里的没有)
  • 命名空间的前缀不一致(比如业务里是fedex:,测试里是ns1:)

第二步:针对性解决匹配问题

根据找到的差异,选对应的方案:

方案1:把动态生成的部分抽成可Mock的依赖

如果差异是时间戳、随机ID这类动态值,别在getAddressRequestPayload()里直接生成,把这部分逻辑抽成单独的类,比如:

// 业务代码里新增依赖
public interface RequestIdGenerator {
    String generateId();
}

public interface TimestampProvider {
    String getCurrentTimestamp();
}

// 在abc.java里注入这些依赖,生成请求时调用它们
SOAPMessage getAddressRequestPayload() {
    String requestId = requestIdGenerator.generateId();
    String timestamp = timestampProvider.getCurrentTimestamp();
    // 用这两个值构建SOAP请求
}

测试时Mock这些依赖,让它们返回固定值:

when(requestIdGenerator.generateId()).thenReturn("fixed-test-id");
when(timestampProvider.getCurrentTimestamp()).thenReturn("2024-05-20T12:00:00Z");

这样业务代码和测试代码生成的请求就能完全一致,Mock的匹配自然就生效了。

方案2:用自定义ArgumentMatcher匹配SOAP内容

如果不想改业务代码,或者差异是XML格式上的小问题(比如空格),可以用Mockito的自定义匹配器,按XML内容来判断是否匹配,而不是依赖SOAPMessage的equals:

先写个匹配器类:

class SOAPContentMatcher extends ArgumentMatcher<SOAPMessage> {
    private final SOAPMessage expectedMsg;

    public SOAPContentMatcher(SOAPMessage expectedMsg) {
        this.expectedMsg = expectedMsg;
    }

    @Override
    public boolean matches(Object argument) {
        if (!(argument instanceof SOAPMessage)) {
            return false;
        }
        try {
            // 转成字符串
            String expectedXml = soapMessageToString(expectedMsg);
            String actualXml = soapMessageToString((SOAPMessage) argument);
            
            // 忽略无关差异,比如时间戳、请求ID
            expectedXml = expectedXml.replaceAll("<RequestTimestamp>.*?</RequestTimestamp>", "<RequestTimestamp>IGNORED</RequestTimestamp>");
            expectedXml = expectedXml.replaceAll("<TransactionId>.*?</TransactionId>", "<TransactionId>IGNORED</TransactionId>");
            
            actualXml = actualXml.replaceAll("<RequestTimestamp>.*?</RequestTimestamp>", "<RequestTimestamp>IGNORED</RequestTimestamp>");
            actualXml = actualXml.replaceAll("<TransactionId>.*?</TransactionId>", "<TransactionId>IGNORED</TransactionId>");
            
            // 还可以去掉所有空格、换行符,避免格式差异
            expectedXml = expectedXml.replaceAll("\\s+", "");
            actualXml = actualXml.replaceAll("\\s+", "");
            
            return expectedXml.equals(actualXml);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

然后Mock的时候用这个匹配器:

when(mockSoapConnection.call(
    argThat(new SOAPContentMatcher(testPayload)), 
    eq(settings.addressUrl())
)).thenReturn(testResponse);

方案3:用ArgumentCaptor验证请求内容

如果不想提前匹配,而是想验证实际调用的请求是否符合预期,可以用Mockito的ArgumentCaptor捕获实际参数,然后在测试里验证:

// 定义Captor
ArgumentCaptor<SOAPMessage> payloadCaptor = ArgumentCaptor.forClass(SOAPMessage.class);

// 执行你的业务方法
abc.doAddressValidation();

// 捕获调用的SOAPMessage参数
verify(mockSoapConnection).call(payloadCaptor.capture(), eq(settings.addressUrl()));

// 验证捕获的请求内容
SOAPMessage actualPayload = payloadCaptor.getValue();
String actualXml = soapMessageToString(actualPayload);
String expectedXml = soapMessageToString(testPayload);

// 同样忽略无关差异后对比
expectedXml = expectedXml.replaceAll("<RequestTimestamp>.*?</RequestTimestamp>", "<RequestTimestamp>IGNORED</RequestTimestamp>");
actualXml = actualXml.replaceAll("<RequestTimestamp>.*?</RequestTimestamp>", "<RequestTimestamp>IGNORED</RequestTimestamp>");

assertEquals(expectedXml.replaceAll("\\s+", ""), actualXml.replaceAll("\\s+", ""));

额外小建议

如果经常要测SOAP服务,试试WireMock这类工具——它可以模拟整个SOAP服务端点,你只需要配置好请求匹配规则,就能验证业务代码发送的SOAP请求是否正确,比Mock SOAPConnection更贴近真实场景。

内容的提问来源于stack exchange,提问作者Saurabh Dwivedi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:18:52