2016-03-13 3 views
8

Ich bin völlig neu in WireMock.Verwenden von WireMock mit SOAP-Webdiensten in Java

Bis jetzt habe ich Schein Antworten mit SOAPUI verwendet. Mein Anwendungsfall ist einfach:

Nur SOAP XML-Anforderungen an verschiedene Endpunkte (http://localhost:9001/endpoint1) abfeuern und vorgefertigte XML-Antwort zurück. MockWrire muss jedoch als eigenständiger Dienst auf einem dedizierten Server bereitgestellt werden, der an einem zentralen Ort agiert, von dem Scheinantworten geliefert werden.

Wollte nur einige Startvorschläge. Wie ich sehen kann, ist WireMock besser geeignet für REST-Webdienste. Also meine Zweifel sind:

1) Muss ich es auf einem Java-Web-Server oder Container bereitstellen, um als immer laufenden eigenständigen Dienst zu handeln. Ich habe gelesen, dass Sie nur durch

mit ausgliedern können
java -jar mockwire.jar --port [port_number] 

2) Muss ich MockWire APIs verwenden? Muss ich Unterricht für meinen Anwendungsfall machen? In meinem Fall werden Anfragen über JUnit-Testfälle zum Mocking ausgelöst.

3) Wie erreiche ich einen einfachen URL-Musterabgleich? Wie oben erwähnt, brauche ich nur einfache Spott, d. H. Antwort erhalten, wenn die Anfrage an http://localhost:9001/endpoint1

4) Gibt es ein besseres/einfacher Rahmen für meine Usecase? Ich lese über Mockable, aber es hat Einschränkungen für 3 Teammitglieder und Demo-Domain in der freien Schicht.

+0

https://github.com/skjolber/mockito-soap-cxf – ThomasRS

Antwort

18

Ich bin der Schöpfer von WireMock.

Ich habe mit WireMock vor kurzem eine Sammlung von SOAP-Interfaces auf einem Client-Projekt gespielt, also kann ich bestätigen, dass es möglich ist. Ob es besser oder schlechter als SOAP UI ist, würde ich sagen, dass es bestimmte Vorteile gibt, aber mit einigen Kompromissen. Ein großer Vorteil ist die relativ einfache Bereitstellung und der programmgesteuerte Zugriff/Konfiguration sowie die Unterstützung von Dingen wie HTTPS und Low-Level-Fault-Injection. Allerdings müssen Sie ein wenig mehr Arbeit für das Parsen und Generieren von SOAP-Nutzdaten leisten - es wird keine Code/Stub-Generierung von WSDL wie SOAP UI machen.

Meine Erfahrung ist, dass Tools wie SOAP UI Sie schneller starten, aber auf lange Sicht zu höheren Wartungskosten führen, wenn Ihre Testsuite trivial wird.

Um Ihre Punkte nacheinander zu adressieren: 1) Wenn Sie möchten, dass Ihre Mocks irgendwo auf einem Server laufen, ist es am einfachsten, das eigenständige JAR wie beschrieben auszuführen. Ich rate davon ab, es in einen Container zu bringen - diese Option gibt es nur, wenn es keine Alternative gibt.

Wenn Sie jedoch nur Integrationstests oder vollständig unabhängige Funktionstests ausführen möchten, empfehle ich die Verwendung der JUnit-Regel. Ich würde sagen, es ist nur eine gute Idee, es in einem dedizierten Prozess auszuführen, wenn entweder a) Sie andere bereitgestellte Systeme in das System einbinden oder b) Sie es aus einer anderen Sprache als JVM verwenden.

2) Sie müssen es auf eine von 3 Arten konfigurieren 1) die Java-API, 2) JSON über HTTP oder 3) JSON-Dateien. 3) ist wahrscheinlich am ehesten dem, was Sie mit SOAP UI gewohnt sind.

3) Siehe http://wiremock.org/stubbing.html für viele Stubbing-Beispiele mit JSON und Java. Da SOAP dazu neigt, an feste Endpunkt-URLs zu binden, möchten Sie wahrscheinlich urlEqualTo(...). Wenn ich SOAP in der Vergangenheit stoppte, neigte ich zur XML-Übereinstimmung für den gesamten Anfragetext (siehe http://wiremock.org/stubbing.html#xml-body-matching).Ich schlage vor, in das Schreiben einiger Java-Builder zu investieren, um den XML-Code für die Anfrage und den Antwortkörper auszugeben, den Sie benötigen.

4) Mock Server und Betamax sind beide ausgereifte Alternativen zu WireMock, aber AFAIK bieten sie keine explizite SOAP-Unterstützung mehr an.

+0

Können Sie sich bitte erklären, wie kann ich relativen/absoluten Pfad angeben, welche für „__files“ zu verwenden, und „Mappings“ Ordner? Ich habe Flaywire-Abhängigkeit durch Maven gesetzt und ein einfaches Servlet erstellt, um 'wireMockServer.start(); 'auf Servlet' init() 'zu machen. Ich nehme an, dass 'withRootDirectory() 'oder' usingFilesUnderDirectory() 'mit' WireMockConfiguration' funktionieren sollte? – Anurag

+0

Ich habe das Problem gelöst, indem ich eine benutzerdefinierte 'FileSource' erstellt und diese an' WireMockConfiguration' übergeben habe. Vielen Dank. – Anurag

+1

@Tom Sie sind nicht sehr produktiv auf StackOverflow, aber ich wollte nur sagen, WireMock ist ein großartiges Stück - vielen Dank! – markdsievers

8

Ich bin über drei Jahre zu spät zu dieser Party, aber es dauerte eine Weile, um das gleiche Problem zu bearbeiten, so dass ich meine Lösung als eine Antwort dokumentieren würde, damit jemand anderen die Kopfschmerzen des manuellen Handelns erspart mit SOAP-Nutzdaten von Grund auf.

Ich habe ein vernünftiges über die Forschung versucht, dieses Problem für meine Integration Testsuite zu lösen. Versuchte alle möglichen Dinge, einschließlich benutzergenerierte CXF-Server, SOAP-UI, eine CGLIB-beeinflusste Bibliothek, die den realen Client in einem Testkontext ersetzt.

Ich endete mit WireMock mit custom request matchers, um alle SOAP -Yness zu behandeln.

Der Kern davon war eine Klasse, die das Unmarshaling von SOAP-Anfragen und Marshaling von SOAP-Antworten behandelte, um Autoren, die JAXB-generierte Objekte benötigten und sich nie mit den Details von SOAP befassen mussten, einen praktischen Wrapper zu bieten.

Antwort Marshalling

/** 
* 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(); 
} 

Anfrage 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; 
} 

Zusätzlich dazu war einige XPath Dienstprogramme für die in der Anfrage-Payload spähen und schauen, was Operation angefordert wurde.

Das gesamte SOAP-Handling war der fiddliest Teil, um zu arbeiten. Von dort erstellen Sie einfach Ihre eigene API, um WireMocks zu ergänzen. Zum Beispiel

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)))); 
} 

und als Ergebnis erhalten Sie eine schöne, schlanke Tests.

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

soapClient.createUser("myUser"); 
+0

Ehrfürchtig, danke für das Teilen! Ich habe https://github.com/tomakehurst/wiremock/issues/759 eingereicht, um dies zu WireMock hinzuzufügen. – mrts