'Using WireMock with SOAP Web Services in Java
I am totally new to WireMock.
Until now, I have been using mock responses using SOAPUI. My use case is simple:
Just firing SOAP XML requests to different endpoints (http://localhost:9001/endpoint1) and getting canned XML response back. But MockWrire has to be deployed as a standalone service onto a dedicated server which will act a central location from where mock responses will be served.
Just wanted some starting suggestions. As I can see WireMock is more suitable towards REST web services. So my doubts are:
1) Do I need to deploy it to a java web server or container to act as always running standalone service. I read that you can just spin off by using
java -jar mockwire.jar --port [port_number]
2) Do I need to use MockWire APIs? Do I need to make classes for my use case? In my case, requests will be triggered via JUnit test cases for mocking.
3) How do I achieve simple URL pattern matching? As stated above, I just need simple mocking i.e get response when request is made to http://localhost:9001/endpoint1
4) Is there a better/easier framework for my usecase? I read about Mockable but it has restrictions for 3 team members and demo domain in free tier.
Solution 1:[1]
I'm WireMock's creator.
I've used WireMock to mock a collection of SOAP interfaces on a client project quite recently, so I can attest that it's possible. As for whether it's better or worse than SOAP UI, I'd say there are some definite upsides, but with some tradeoffs. A major benefit is the relative ease of deployment and programmatic access/configuration, and support for things like HTTPS and low-level fault injection. However, you need to do a bit more work to parse and generate SOAP payloads - it won't do code/stub generation from WSDL like SOAP UI will.
My experience is that tools like SOAP UI will get you started faster, but tend to result in higher maintenance costs in the long run when your test suite grows beyond trivial.
To address your points in turn: 1) If you want your mocks to run on a server somewhere, the easiest way to do this is to run the standalone JAR as you've described. I'd advise against trying to deploy it to a container - this option really only exists for when there's no alternative.
However, if you just want to run integration tests or totally self-contained functional tests, I'd suggest using the JUnit rule. I'd say it's only a good idea to run it in a dedicated process if either a) you're plugging other deployed systems into it, or b) you're using it from a non-JVM language.
2) You'd need to configure it in one of 3 ways 1) the Java API, 2) JSON over HTTP, or 3) JSON files. 3) is probably closest to what you're used to with SOAP UI.
3) See 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). I'd suggest investing in writing a few Java builders to emit the request and response body XML you need.
4) Mock Server and Betamax are both mature alternatives to WireMock, but AFAIK they don't offer any more explicit SOAP support.
Solution 2:[2]
I'm over three years late to this party, but it took me a while to work through the same problem so I though it worthy of documenting my solution as an answer so it might save someone else the headache of manually dealing with SOAP payloads from scratch.
I did a reasonable about of research trying to solve this problem for my integration test suite. Tried all sorts of things including CXF custom generated servers, SOAP-UI, a CGLIB influenced library that replaces the real client in a test context.
I ended up using WireMock with custom request matchers to handle all the SOAP
-yness.
The gist of it was a class that handled unmarshaling of SOAP requests and marshaling of SOAP responses in order to provide a convenient wrapper to test authors that only required JAXB generated objects and never had to concern themselves with the details of SOAP.
Response Marshaling
/**
* 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();
}
Request Unmarshaling
/**
* 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;
}
In addition to this was some XPath utilities for peeking into the request payload and looking at what operation was being requested.
All the SOAP handling was the fiddliest part to get working. From there it's just creating your own API to supplement WireMocks. For example
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))));
}
and as a result you end up with a nice, lean tests.
SoapContext context = new SoapContext(...) // URIs, QName, Prefix, ect
context.stubOperation("createUser", CreateUser.class, (u) -> "myUser".equals(u.getUserName()), new CreateUserResponse());
soapClient.createUser("myUser");
Solution 3:[3]
- I running the wiremock server as standalone
I create a mapping.json file, put in my mock project 'mappings' folder
{"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" }}}
I create a response xml file, put it in the '__files' folder
<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>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | gliptak |
Solution 2 | Ivan Baranov |
Solution 3 | Jin |