Использование WireMock с веб-службами SOAP в Java

Я совершенно не знаком с WireMock.

До сих пор я использовал фиктивные ответы с использованием SOAPUI. Мой вариант использования прост:

Просто отправьте запросы SOAP XML на разные конечные точки (http://localhost:9001/endpoint1) и получите обратно шаблонный ответ XML. . Но MockWrire необходимо развернуть как отдельную службу на выделенном сервере, который будет действовать как центральное место, откуда будут отправляться фиктивные ответы.

Просто хотел несколько начальных предложений. Как я вижу, WireMock больше подходит для веб-сервисов REST. Итак, мои сомнения:

1) Нужно ли мне развертывать его на веб-сервере или контейнере java, чтобы он работал как всегда работающий автономный сервис. Я читал, что вы можете просто отключиться, используя

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

2) Нужно ли мне использовать API MockWire? Нужно ли мне создавать классы для моего варианта использования? В моем случае запросы будут запускаться через тестовые примеры JUnit для имитации.

3) Как добиться простого сопоставления с шаблоном URL? Как указано выше, мне просто нужно простое издевательство, то есть получить ответ при запросе к http://localhost:9001/endpoint1

4) Есть ли лучшая / более простая структура для моего варианта использования? Я читал о Mockable, но у него есть ограничения для 3 членов команды и демо-домен на бесплатном уровне.


person Anurag    schedule 13.03.2016    source источник
comment
github.com/skjolber/mockito-soap-cxf   -  person ThomasRS    schedule 27.10.2016


Ответы (3)


Я создатель WireMock.

Совсем недавно я использовал WireMock для имитации коллекции интерфейсов SOAP в клиентском проекте, поэтому могу подтвердить, что это возможно. Что касается того, лучше он или хуже, чем пользовательский интерфейс SOAP, я бы сказал, что есть определенные преимущества, но с некоторыми компромиссами. Основным преимуществом является относительная простота развертывания и программного доступа / конфигурации, а также поддержка таких вещей, как HTTPS и внедрение низкоуровневых ошибок. Однако вам нужно проделать немного больше работы для синтаксического анализа и генерации полезной нагрузки SOAP - он не будет генерировать код / ​​заглушку из WSDL, как это сделает пользовательский интерфейс SOAP.

Мой опыт показывает, что такие инструменты, как SOAP UI, позволят вам начать работу быстрее, но, как правило, приводят к более высоким затратам на обслуживание в долгосрочной перспективе, когда ваш набор тестов вырастет за пределы тривиального.

Чтобы ответить на ваши вопросы по очереди: 1) Если вы хотите, чтобы ваши макеты запускались где-то на сервере, самый простой способ сделать это - запустить автономный JAR, как вы описали. Я бы не советовал пытаться развернуть его в контейнере - этот вариант действительно существует только тогда, когда нет альтернативы.

Однако, если вы просто хотите запускать интеграционные тесты или полностью автономные функциональные тесты, я бы предложил использовать правило JUnit. Я бы сказал, что запускать его в отдельном процессе - это хорошая идея, если: а) вы подключаете к нему другие развернутые системы, или б) вы используете его на языке, отличном от JVM.

2) Вам нужно будет настроить его одним из трех способов: 1) Java API, 2) JSON через HTTP или 3) файлы JSON. 3), вероятно, наиболее близок к тому, к чему вы привыкли с пользовательским интерфейсом SOAP.

3) См. http://wiremock.org/stubbing.html для множества примеров заглушек с использованием как JSON, так и Джава. Поскольку SOAP имеет тенденцию связываться с фиксированными URL-адресами конечных точек, вам, вероятно, понадобится urlEqualTo(...). Когда я раньше вставлял SOAP-протокол, я старался соответствовать XML по всему телу запроса (см. http://wiremock.org/stubbing.html#xml-body-matching). Я бы посоветовал вложиться в написание нескольких Java-компоновщиков, которые будут генерировать XML-текст тела запроса и ответа, который вам нужен.

4) Мок-сервер и Betamax являются зрелыми альтернативами WireMock, но, AFAIK, они не предлагают более явной поддержки SOAP.

person Tom    schedule 13.03.2016
comment
Не могли бы вы объяснить, как я могу указать, какой относительный / абсолютный путь использовать для папки __files и mappings? Я установил зависимость mockwire через maven и создал простой сервлет для выполнения wireMockServer.start(); на сервлете init(). Я предполагаю, что withRootDirectory() или usingFilesUnderDirectory() с WireMockConfiguration должны работать? - person Anurag; 14.03.2016
comment
Я решил проблему, создав пользовательский FileSource и передав его в WireMockConfiguration. Спасибо. - person Anurag; 14.03.2016
comment
@Tom Вы не слишком успешны в StackOverflow, но я просто хотел сказать, что WireMock - отличный комплект - большое спасибо! - person markdsievers; 12.10.2016
comment
Том, не могли бы вы привести несколько примеров? - person Rafal Enden; 12.04.2017
comment
@RafalEnden Весь код, необходимый для рабочего примера, находится в моем ответе ниже. - person markdsievers; 15.04.2017
comment
Есть ли способ издеваться над вложениями SOAP? См. stackoverflow.com/questions/44966961/ - person Dario Zamuner; 07.07.2017

Я опоздал на эту вечеринку более чем на три года, но мне потребовалось время, чтобы проработать ту же проблему, поэтому я подумал, что стоит задокументировать мое решение в качестве ответа, чтобы это могло избавить кого-то от головной боли вручную работать с полезными нагрузками SOAP из царапать.

Я провел разумное исследование, пытаясь решить эту проблему для своего набора интеграционных тестов. Пробовал всевозможные вещи, включая настраиваемые серверы CXF, SOAP-UI, библиотеку под влиянием CGLIB, которая заменяет реального клиента в тестовом контексте.

В итоге я использовал WireMock с настраиваемыми сопоставителями запросов для обработки всех SOAP -yness.

Суть этого была в классе, который обрабатывал немаршалинг запросов SOAP и маршалинг ответов SOAP, чтобы предоставить авторам тестов удобную оболочку, которая требовала только сгенерированные JAXB объекты и никогда не заботилась о деталях SOAP.

Маршалинг ответа

/**
 * 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");
person markdsievers    schedule 23.10.2016
comment
Отлично, спасибо, что поделились! Я подал github.com/tomakehurst/wiremock/issues/759, чтобы добавить это в WireMock. - person mrts; 21.09.2017
comment
У меня проблема. У меня есть мыло 1.2. Я создал конечную точку, но это дает мне ошибку: сообщение soap 1.2 недействительно при отправке на конечную точку только soap 1.1. Любая помощь будет оценена по достоинству. Спасибо! - person richa_v; 06.12.2019

  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>

person Jin    schedule 26.06.2018