在Java中使用WireMock和SOAP Web服务

我对WireMock完全不熟悉 。

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

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

只想要一些开始的建议。 我可以看到WireMock更适合REST Web服务。 所以我的怀疑是:

1)我是否需要将其部署到Java Web服务器或容器以充当始终运行的独立服务。 我读到你可以通过使用来剥离

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

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

3)如何实现简单的URL模式匹配? 如上所述,我只需要简单的模拟,即在向http:// localhost:9001 / endpoint1发出请求时获取响应

4)我的用例是否有更好/更容易的框架? 我读到了Mockable,但它对3个团队成员和免费层的演示域有限制。

我是WireMock的创造者。

我最近使用WireMock在客户端项目上模拟了SOAP接口的集合,所以我可以certificate它是可能的。 至于它是否比SOAP UI更好或更差,我会说有一些明确的优势,但有一些权衡。 一个主要的好处是相对容易部署和编程访问/配置,以及对HTTPS和低级故障注入等方面的支持。 但是,您需要做更多的工作来解析和生成SOAP有效负载 – 它不会像SOAP UI那样从WSDL生成代码/存根。

我的经验是,像SOAP UI这样的工具可以让你更快地开始,但是从长远来看,当你的测试套件变得越来越琐碎时,往往会导致更高的维护成本。

要依次解决您的观点:1)如果您希望您的模拟在某个服务器上运行,最简单的方法是按照您的描述运行独立的JAR。 我建议不要试图将它部署到容器中 – 这个选项实际上只存在于没有其他选择时。

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

2)您需要以3种方式之一配置它:1)Java API,2)基于HTTP的JSON,或3)JSON文件。 3)可能与您习惯使用SOAP UI的方式最接近。

3)有关使用JSON和Java的大量存根示例,请参阅http://wiremock.org/stubbing.html 。 由于SOAP倾向于绑定到固定端点URL,因此您可能需要urlEqualTo(...) 。 当我在过去使用SOAP时,我倾向于在整个请求体上进行XML匹配(请参阅http://wiremock.org/stubbing.html#xml-body-matching )。 我建议投资编写一些Java构建器来发出所需的请求和响应主体XML。

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

我参加这个聚会已经晚了三年,但我花了一些时间才解决同样的问题,所以我觉得值得记录我的解决方案作为答案,这样可以省去别人手头处理SOAP有效负载的麻烦。刮。

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

我最终使用WireMock和自定义请求匹配器来处理所有SOAP -yness。

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

响应Marshaling

 /** * Accepts a WebService response object (as defined in the WSDL) and marshals * to a SOAP envelope String. */ public  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 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 deserializeSoapRequest(String soapRequest, Class clazz) { XMLInputFactory xif = XMLInputFactory.newFactory(); JAXBElement 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 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  void stubOperation(String operation, Class clazz, Predicate 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’文件夹中

    twelve dollars