在 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");
- 我运行 wiremock 服务器是独立的
我创建了一个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"
}}}
我创建一个响应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>
我对 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");
- 我运行 wiremock 服务器是独立的
我创建了一个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" }}}
我创建一个响应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>