在 Java 中将 WireMock 与 SOAP Web 服务结合使用

Using WireMock with SOAP Web Services in Java

我对 WireMock 完全陌生。

到目前为止,我一直在使用 SOAPUI 模拟响应。我的用例很简单:

只需将 SOAP XML 请求发送到不同的端点 (http://localhost:9001/endpoint1) 并获得固定的 XML 响应。但是 MockWrire 必须作为独立服务部署到专用服务器上,该服务器将充当提供模拟响应的中心位置。

只是想要一些入门建议。如我所见,WireMock 更适合 REST Web 服务。所以我的疑惑是:

1) 我是否需要将其部署到 java 网络服务器或容器以始终作为 运行 独立服务使用。我读到您可以使用

分拆
java -jar mockwire.jar --port [port_number]

2) 我需要使用 MockWire API 吗?我需要为我的用例制作 类 吗?在我的例子中,请求将通过 JUnit 测试用例触发以进行模拟。

3) 如何实现简单的URL 模式匹配?如上所述,我只需要简单的模拟,即在向 http://localhost:9001/endpoint1

发出请求时得到响应

4) 我的用例是否有 better/easier 框架?我阅读了有关 Mockable 的信息,但它在免费层中对 3 名团队成员和演示域有限制。

我是 WireMock 的创建者。

我最近使用 WireMock 在客户端项目上模拟了一组 SOAP 接口,所以我可以证明这是可能的。至于它比 SOAP UI 好还是坏,我想说它有一些明确的优点,但也有一些折衷。一个主要的好处是相对容易部署和编程 access/configuration,并支持 HTTPS 和低级故障注入等。但是,您需要做更多的工作来解析和生成 SOAP 有效负载 - 它不会像 SOAP UI 那样从 WSDL 生成 code/stub。

我的经验是,像 SOAP UI 这样的工具可以让您更快地起步,但是当您的测试套件变得过于琐碎时,长期 运行 往往会导致更高的维护成本。

依次解决您的问题: 1) 如果您希望您的模拟 运行 在某个服务器上,最简单的方法是 运行 您所描述的独立 JAR。我建议不要尝试将其部署到容器中 - 这个选项实际上只存在于没有其他选择的情况下。

但是,如果您只想 运行 集成测试或完全独立的功能测试,我建议使用 JUnit 规则。我会说,如果 a) 您正在将其他已部署的系统插入其中,或者 b) 您正在从非 JVM 语言中使用它,那么在专用进程中 运行 它只是一个好主意。

2) 您需要通过以下 3 种方式之一对其进行配置 1) Java API、2) JSON 通过 HTTP,或 3) JSON 文件。 3) 可能最接近您习惯使用 SOAP UI.

3) 参见 http://wiremock.org/stubbing.html for lots of stubbing examples using both JSON and Java. Since SOAP tends to bind to fixed endpoint URLs, you probably want urlEqualTo(...). When I've stubbed SOAP in the past I've tended to XML match on the entire request body (see http://wiremock.org/stubbing.html#xml-body-matching)。我建议投资编写一些 Java 构建器来发出你需要的请求和响应主体 XML。

4) Mock Server and Betamax 都是 WireMock 的成熟替代品,但据我所知,它们不提供任何更明确的 SOAP 支持。

我迟到了三年多,但我花了一些时间来解决同样的问题,所以我认为值得将我的解决方案记录下来作为答案,这样它可能会让其他人免于手动操作的麻烦从头开始处理 SOAP 负载。

我做了一些合理的研究,试图为我的集成测试套件解决这个问题。尝试了各种各样的东西,包括 CXF 自定义生成的服务器、SOAP-UI,一个受 CGLIB 影响的库,它在测试上下文中替换了真实的客户端。

我最终使用带有 custom request matchers 的 WireMock 来处理所有 SOAP-yness。

它的要点是 class 处理 SOAP 请求的解组和 SOAP 响应的编组,以便为​​只需要 JAXB 生成的对象而无需关心的测试作者提供方便的包装器肥皂的细节。

响应封送处理

/**
 * Accepts a WebService response object (as defined in the WSDL) and marshals
 * to a SOAP envelope String.
 */
public <T> String serializeObject(T object) {
    ByteArrayOutputStream byteArrayOutputStream;
    Class clazz = object.getClass();
    String responseRootTag = StringUtils.uncapitalize(clazz.getSimpleName());
    QName payloadName = new QName("your_namespace_URI", responseRootTag, "namespace_prefix");

    try {
        JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
        Marshaller objectMarshaller = jaxbContext.createMarshaller();

        JAXBElement<T> jaxbElement = new JAXBElement<>(payloadName, clazz, null, object);
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        objectMarshaller.marshal(jaxbElement, document);

        SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
        SOAPBody body = soapMessage.getSOAPPart().getEnvelope().getBody();
        body.addDocument(document);

        byteArrayOutputStream = new ByteArrayOutputStream();
        soapMessage.saveChanges();
        soapMessage.writeTo(byteArrayOutputStream);
    } catch (Exception e) {
        throw new RuntimeException(String.format("Exception trying to serialize [%s] to a SOAP envelope", object), e);
    }

    return byteArrayOutputStream.toString();
}

请求解组

/**
 * Accepts a WebService request object (as defined in the WSDL) and unmarshals
 * to the supplied type.
 */
public <T> T deserializeSoapRequest(String soapRequest, Class<T> clazz) {

    XMLInputFactory xif = XMLInputFactory.newFactory();
    JAXBElement<T> jb;
    try {
        XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(soapRequest));

        // Advance the tag iterator to the tag after Body, eg the start of the SOAP payload object
        do {
            xsr.nextTag();
        } while(!xsr.getLocalName().equals("Body"));
        xsr.nextTag();

        JAXBContext jc = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        jb = unmarshaller.unmarshal(xsr, clazz);
        xsr.close();
    } catch (Exception e) {
        throw new RuntimeException(String.format("Unable to deserialize request to type: %s. Request \n %s", clazz, soapRequest), e);
    }

    return jb.getValue();
}

private XPath getXPathFactory() {

    Map<String, String> namespaceUris = new HashMap<>();
    namespaceUris.put("xml", XMLConstants.XML_NS_URI);
    namespaceUris.put("soap", "http://schemas.xmlsoap.org/soap/envelope/");       
    // Add additional namespaces to this map        

    XPath xpath = XPathFactory.newInstance().newXPath();

    xpath.setNamespaceContext(new NamespaceContext() {
        public String getNamespaceURI(String prefix) {
            if (namespaceUris.containsKey(prefix)) {
                return namespaceUris.get(prefix);
            } else {
                return XMLConstants.NULL_NS_URI;
            }
        }

        public String getPrefix(String uri) {
            throw new UnsupportedOperationException();
        }

        public Iterator getPrefixes(String uri) {
            throw new UnsupportedOperationException();
        }
    });

    return xpath;
}

除此之外还有一些 XPath 实用程序,用于查看请求负载并查看请求的操作。

所有 SOAP 处理是开始工作中最繁琐的部分。从那里它只是创建您自己的 API 来补充 WireMocks。例如

public <T> void stubOperation(String operation, Class<T> clazz, Predicate<T> predicate, Object response) {
    wireMock.stubFor(requestMatching(
                     new SoapObjectMatcher<>(context, clazz, operation, predicate))
                    .willReturn(aResponse()
                    .withHeader("Content-Type", "text/xml")
                    .withBody(serializeObject(response))));
}

结果你得到了一个很好的精益测试。

SoapContext context = new SoapContext(...) // URIs, QName, Prefix, ect
context.stubOperation("createUser", CreateUser.class, (u) -> "myUser".equals(u.getUserName()), new CreateUserResponse());

soapClient.createUser("myUser");
  1. 我运行 wiremock 服务器是独立的
  2. 我创建了一个mapping.json文件,放在我的模拟项目'mappings'文件夹中

    {"request": { "url": "/webservicesserver/numberconversion", "method": "POST"}, "response": { "status": 200, "bodyFileName": "response.xml", "headers": { "Server": "Microsoft-IIS/8.0", "Access-Control-Allow-Origin": "http://www.dataaccess.com", "Access-Control-Allow-Methods": "GET, POST", "Connection": "Keep-Alive", "Web-Service": "DataFlex 18.1", "Access-Control-Allow-Headers": "content-type", "Date": "Tue, 26 Jun 2018 07:45:47 GMT", "Strict-Transport-Security": "max-age=31536000", "Cache-Control": "private, max-age=0", "Access-Control-Allow-Credentials": true, "Content-Length": 352, "Content-Type": "application/soap+xml; charset=utf-8" }}}

  3. 我创建一个响应xml文件,把它放在'__files'文件夹中

    <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" > <soap:Body> <m:NumberToDollarsResponse xmlns:m="http://www.dataaccess.com/webservicesserver/"> <m:NumberToDollarsResult>twelve dollars</m:NumberToDollarsResult> </m:NumberToDollarsResponse> </soap:Body> </soap:Envelope>