Introducción
Aplicaciones como SOAP UI nos permiten implementar pruebas de manera visual sobre servicios SOAP, sin embargo, si queremos llevar estas pruebas a un proceso de Integración Continua o queremos realizar pruebas integradas con otras tecnologías como Web o Mobile, es necesario llevar nuestra implementación a herramientas mucho más versátiles.
Hablamos de RestAssured. Una librería escrita en Java que nos permite realizar peticiones HTTP/S de manera sencilla.
Análogamente, se podría realizar este tipo de implementación con otros lenguajes y librerias , como puede ser Axios y Javascript. Pero eso queda para otro artículo de la serie que ha iniciado el compañero Eutropio Prieto González en esta semana con https://www.enmilocalfunciona.io/introduccion-a-pruebas-de-servicios-rest-api-con-soapui/ y que continúa con este artículo que espero disfrutes.
Qué es SOAP?
SOAP es protocolo de comunicación Cliente-Servidor.
Define su modelo de comunicación mediante archivos de especificación llamados WSDL. Estos archivos indican las operaciones disponibles en el servicio , así como la información requerida por cada una de ellas para poder responder adecuadamente a la petición.
En Soap todo parte de una URI (Identificador de Recursos Uniforme) que identifica a un Servicio. Está compuesta de :
- Protocolo : Http/ Https
- Host: myservidor.entorno.com
- Puerto : 60041
- Nombre del servicio
- Extensión del servicio (svc/ asmx..)
Ejemplo URI
https://myservidor.club.int:60041/ServiceSales.svc -> URI de un servicio de ventas.
Una vez conocemos la URI de nuestro servicio, podemos acceder a la definición WDSL añadiendo el sufijo «?wsdl«.
Conocer la definición de un servicio nos va a permitir interactuar con él de la manera adecuada. Eso quiere decir, que sabemos previamente a la generación de la consulta, que parámetros puede aceptar cada Operación que llamamos.
Componentes de una petición SOAP
Parámetro |
Descripción |
Ejemplo |
---|---|---|
URI del Servicio |
URL que identifica al servicio. |
https://myservice:62222 |
Action asociada a la operación que queremos llamar. |
http://tempuri.org/IServiceSales/GetSales |
|
Username |
Usuario para la authenticación OUTGOING WSS |
MYUSER |
Password |
Contraseña para la authenticación OUTGOING WSS |
MYPASSWORD |
Soap Message |
Contenido a incluir en el body de la consulta. |
|
Mensaje SOAP
- Soap Envelope [soapenv:Envelope/s:Envelope]: Incluye el schema asociado al propio Envelope y al Message. También incluye el namespace utilizado en el Body.
Para conocer la versión de SOAP Envelope utiliza nuestro Servicio , debemos acceder al atributo Namespace URI (xmlns:soap o xmlns:soapenv) definido en el Envelope del Request o del Response
- Header [soapenv:Header/s:Header]: Incluye meta-información de la request (información de authenticación, definición de la acción a realizar, etc..
- Body [soapenv:Body/s:Body]: Cuerpo de la petición, en formato XML. Es la información propia de la petición. Por ejemplo , si el servicio es de recuperación de un evento, el mensaje debería contener el identificador de dicho evento. Las etiquetas incluidas en el body incluyen el namespace prefijado.
Creación de un Mensaje SOAP
Ahora que conocemos las componentes de una petición SOAP, vamos a ver como generarlos, dónde y cómo usarlos.
Spoiler: Todos los componentes deben incluirse de manera parametrizada en la capa de servicios.
NO existe petición posible hacia un Servicio SOAP sin un SOAP Message.
Plugin Maven creación POJOs
Para poder manipular un mensaje desde Java es necesario convertirlo previamente a un POJO (Plain Old Java Object). Para ello el vamos a hacer uso de un plugin de maven que nos va a permitir generar estos POJOs de manera automática.
En el archivo pom.xml de nuestro repositorio deberemos incluir dependencia y la configuración del plugin maven-jaxb2-plugin añadiendo tantas urls en el atributo <schema> como Servicios vayamos a validar.
<dependency>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>${mavn.jaxb2.plugin.verison}</version>
</dependency>
...
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemas>
<schema>
<url>https://mydomain.int:641/ServiceSales.svc?wsdl</url>
</schema>
</schemas>
<generateDirectory>${project.build.directory}/generated-sources/</generateDirectory>
<removeOldOutput>true</removeOldOutput>
<debug>true</debug>
<!-- DTD, XMLSCHEMA, RELAXNG, RELAXNG_COMPACT, WSDL, AUTODETECT. -->
<schemaLanguage>WSDL</schemaLanguage>
</configuration>
</plugin>
Una vez finalizada la configuración del plugin , deberemos ejecutar el comando:
mvn clean install -DskipTests
Al ejecutar este comando, se descargaran en la capeta /target los POJOs asociados a todas las Operaciones del Servicio SOAP.
Creación de un Servicio
Ahora que tenemos clara la forma de componer un Mensaje SOAP , vamos a ver cómo crear un Servicio QA que valide alguna de las Operaciones del Servicio SOAP.
Lo primero que tenemos que hacer es crear nuestra clase Java en la carpeta /src/main/java/services/.
@NoArgsConstructor
public class SalesService {
private static final String DEFAULT_BODY = "back/soap/input/GetSales.xml";
private static final String ACTION = "http://tempuri.org/IServiceSales/GetSales";
@Getter
public Request request;
@Getter
public Response response;
/**
* GET Sales
* @return SalesService
*/
public SalesService getSales(File fileBody) {
this.request = RestAssured
.given()
.relaxedHTTPSValidation()
.log()
.all()
.redirects()
.follow(false);
this.response = getRequest()
.baseUri(SALES_BACK_URI)
.contentType("application/soap+xml")
.body(fileBody)
.post(SALES_SERVICE);
return this;
}
/**
* GET Sales from default template
*
* @return SalesService
*/
public SalesService getSales() {
return getSales(FolderManager.getResourceFile(DEFAULT_BODY));
}
/**
* GET Sales from POJO
* @return SalesService
*/
public SalesService getSales(SalesRequestBody salesRequestBody) {
this.request = RestAssured
.given()
.relaxedHTTPSValidation()
.log()
.all()
.redirects()
.follow(false);
this.response = getRequest()
.baseUri(SALES_BACK_URI)
.contentType("application/soap+xml")
.body(salesRequestBody)
.post(SALES_SERVICE);
return this;
}
}
Nuestra clase debe finalizar con el sufijo Service, de esta manera podremos reconocerla fácilmente.
Como vemos en la clase SalesService que, valida un sólo servicio, el de ventas, incluye 2 métodos similares pero que se diferencian en el parámetro que reciben.
En el primer caso recibimos un objeto del tipo File (fileBody), que utilizamos directamente en el body del request. En el segundo caso recibimos un objeto POJO (salesRequestBody).
Creación de un Test
Ya está todos listo, ahora sólo tenemos que definir nuestros tests, que básicamente como vamos a ver , lo único que hacen es llamar a nuestro servicio y aplicar alguna aserción sobre los resultados ya serializados en un Objecto(POJO).
Creación mediante un POJO
public class SalesTestsUsingPojo {
@Description("Validate GET Availability")
public void validateGetSales() {
//Instanciamos nuestro POJO y le seteamos el id y la fxChannel
SalesRequestBody salesBody = new SalesRequestBody();
JAXBElement<String> idSale = new JAXBElement<>(new QName("IdSale"), String.class , "2983");
salesBody.setIdSale(idSale);
salesBody.setFxChannel("2023-01-01"));
//Llamamos al servicio SalesService con el body creado.
SalesService salesService = new SalesService()
.getSales(salesBody);
Assert.assertEquals(salesService.statusCode(), 200,
"Get Sales status response not expected");
//Convertimos la respuesta en un objeto GetSales y validamos que se ha serializado correctamente.
GetSales getSales = salesService.as(GetSales.class);
Assert.assertFalse(Objects.isNull(getSales),
"GetAvailability not found");
//Extraemos los valores que queramos de manera sencilla (por su nombre) y validamos que corresponden con la información esperada. (Por ejemplo que el id del artículo recibido es el mismo que consultamos).
String idSaleResponse = getSales.getId().getValue();
Assert.assertEquals(idSaleResponse, idSale
"Sale recibed are not the expected.");
}
}
Creación mediante un archivo de texto/xml
Si hemos elegido realizar la petición al Servicio mediante un archivo de texto/xml , deberemos disponer de en archivo xml indicado en la ruta indicada en el DEFAULT_BODY.
Este es un ejemplo
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
<soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken >
<wsse:Username>MYUSER</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">MYPASSWORD</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
<wsa:Action>http://tempuri.org/IServiceSales/GetSales</wsa:Action>
<wsa:To>https://mydomain.int:641/ServiceSales.svc</wsa:To>
</soap:Header>
<soap:Body>
<tem:GetSales>
<tem:idSale>2983</tem:idSale>
<tem:fxChannel>2023-01-30</tem:fxChannel>
</tem:GetSales>
</soap:Body>
</soap:Envelope>
public class SalesTestsUsingDefinedSoapXmlBody {
@Description("Validate GET Availability")
public void validateGetSales() {
//Llamamos al servicio SalesService sin body, así nuestro servicio utilizará el archivo xml definido como DEFAULT_BODY.
SalesService salesService = new SalesService()
.getSales();
Assert.assertEquals(salesService.statusCode(), 200,
"Get Sales status response not expected");
//Convertimos la respuesta en un objeto GetSales y validamos que se ha serializado correctamente.
GetSales getSales = salesService.as(GetSales.class);
Assert.assertFalse(Objects.isNull(getSales),
"GetAvailability not found");
//Extraemos los valores que queramos de manera sencilla (por su nombre) y validamos que corresponden con la información esperada. (Por ejemplo que el id del artículo recibido es el mismo que consultamos).
String idSaleResponse = getSales.getId().getValue();
Assert.assertEquals(idSaleResponse, idSale
"Sale recibed are not the expected.");
}
}
Descripción clases
Package |
Clase |
Descripción |
---|---|---|
javax.xml.namespace |
QName |
(Namespace URI + localpart prefix) |
javax.xml.soap |
SOAPMessage |
Clase principal para todos los mensajes SOAP. Contiene SOAPPart, SOAPEnvelope, SOAPBody, SOAPHeader. |
javax.xml.soap |
SOAPEnvelope |
Contenedor para los elementos SOAPHeader y SOAPBody. |
javax.xml.soap |
SOAPHeader |
Representación del elemento Header. |
javax.xml.soap |
SOAPHeaderElement |
Objeto que representa el contenido de un elemento en el Header. |
javax.xml.soap |
SOAPBody |
Representación del elemento Body. |
javax.xml.soap |
SOAPPart |
Contenedor de elementos incluídos en un SoapMessage. (SOAPHeader o SOAPBody) |
Glosario
Operación: Nombre de la acción llamada en el Servicio. Todas las operaciones disponibles en un Servicio están disponibles en el archivo de definición WDSL.
Servicio SOAP: Agrupación de Operaciones.
Servicio de QA : Servicio creado por el equipo de QA mediante este documento para la validación de Servicios SOAP.
Errores frecuentes
Error certificado
Si recibimos un error indicando que no se puede realizar la conexión debido a la falta de certificado, podemos descargar el mismo desde chrome e importarlo a nuestro JDK mediante el programa keytool.
keytool -importcert -file .srcmainresourcescertsclub.int.crt -alias ave-club-int -keystore 'C:Program FilesJavajre1.8.0_341libsecuritycacerts'
Referencias
https://javaee.github.io/jaxb-v2/doc/user-guide/
https://hevodata.com/learn/soap-vs-rest-apis/
https://www.baeldung.com/maven-wsdl-stubs
https://github.com/highsource/maven-jaxb2-plugin/wiki/Specifying-What-To-Compile