Pruebas Unitarias a JPA y servicios REST con Arquillian + Payara. (3/4) - Probando el Servicio

Ya tenemos la persistencia (JPA), ya tenemos la lógica de negocio (EJB) y con sus respectivas pruebas, ahora nos falta la parte principal del microservicio: el servicio.

Así que haremos un par de servicios y sus respectivas pruebas con Arquillian.



Hasta el momento hemos hecho las pruebas usando las notación @Inject como si estuviéramos en un entorno Java EE. Ahora, los servicios son probados como una llamada a un servidor web. Entonces, las llamadas se harán por HTTP.

Creando los servicios

Comenzaremos por crear la clase ProductREST.java, será el servicio para buscar productos, y este es el código.

@Path("product")
@Produces(MediaType.APPLICATION_JSON)
@Singleton
public class ProductREST {

    @Inject
    private ProductFacade facade; //instanciamos el EJB

    @GET
    public List<Product> findByName(
            @QueryParam("name") @DefaultValue("") String name
    ) {
        return facade.findByName(name); //solo llamamos al método del EJB
    }
}


Bastante fácil y simple. Solo le pasamos el nombre del producto (o parte del nombre); y si no le pasa un texto en el parámetro, tomará el valor "" para evitar el valor nulo. Notemos el valor que está respondiendo: una lista de Product.

Ahora crearemos otro servicio que realizará la venta del producto. Este tendrá un método de tipo POST que recibirá los parámetros de algún formulario. La clase a crear se llamará SellREST.java, y este es su código:

@Path("sell")
@Produces(MediaType.APPLICATION_JSON)
@Singleton
public class SellREST {

    @Inject
    private ProductFacade productFacade;

    @POST
    @Produces(MediaType.TEXT_PLAIN)

    public Response sell(
            @FormParam("productId") long productId,
            @FormParam("quantity") int quantity
    ) {
        try {
            double mount = productFacade.sell(productId, quantity);
            return Response.ok(mount).build();
        } catch (NoProductException ex) {
            return Response.status(Response.Status.NOT_FOUND).build();
        } catch (OutStockException ex) {
            return Response.status(Response.Status.BAD_REQUEST).build();
        }
    }
}


Hay varias maneras de devolver los errores al cliente: por códigos de error HTTP, o por un json con un campo que dice "error" indicando el error. Todo depende cómo lo acuerden en el diseño. En este ejemplo estoy usando los códigos de error HTTP: si no se encuentra el producto, entonces lanza el error 404, y si se excede del stock lanzará el error 400. En caso que esté todo bien, debería devolver el monto de la venta del producto.

Ya casi vamos a terminar, solo nos falta la clase que configura los servicios en el entorno. Esta clase se llamará ApplicationConfig y este es su código:

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new java.util.HashSet<>();
        addRestResourceClasses(resources);
        return resources;
    }

    private void addRestResourceClasses(Set<Class<?>> resources) {
        resources.add(com.apuntesdejava.arquillian.rest.ProductREST.class);
        resources.add(com.apuntesdejava.arquillian.rest.SellREST.class);
    }

}


En un entorno real de Java EE no necesita más que las anotaciones para declarar qué clase es la encargada de configurar el entorno REST. cof cof, spring aún necesita de xml, cof cof.

Y listo, ya tenemos los servicios, ahora vamos a probarlos.

Configurando las pruebas para REST

Básicamente, la configuración es la misma que en el post anterior. Solo que, vamos a necesitar el URL donde está desplegado el war en nuestro entorno de prueba. Crearemos la clase RESTTest y esta es la primera parte del código:

@RunWith(Arquillian.class)
public class RESTTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(RESTTest.class);

    @ArquillianResource
    private URL deploymentURL; //el URL donde se desplegó el war

    @Deployment
    public static WebArchive createDeployment() {
        return ShrinkWrap.create(WebArchive.class, "payara-arquillian-rest.war")
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addClasses(ProductREST.class,
                        SellREST.class,
                        ProductFacade.class,
                        SellFacade.class,
                        ApplicationConfig.class,
                        Product.class,
                        Sell.class);
    }
//...


Notemos que en la línea 45 Arquillian injectará el valor del URL donde estará desplegado el war. Las líneas 51 y 52 son las clases REST que incluiremos en el entorno para el despliegue y así hagamos las pruebas.

Probado el servicio GET

Necesitamos crear un cliente HTTP y que procese las peticiones como JSON, por ello crearemos el siguiente método:

    private static Client createClient() {
        return ClientBuilder
                .newBuilder()
                .register(JacksonJaxbJsonProvider.class) //para procesar las peticiones como JSON
                .build();
    }


Ahora sí, crearemos nuestra prueba al método de buscar productos:

    @Test
    @UsingDataSet("datasets/products.yml")
    public void testFindProduct() throws URISyntaxException {
        Client client = createClient(); //creamos el cliente
        WebTarget target = client.target(deploymentURL.toURI()) //registramos el servidor...
                .path("webresources/product") //.. el path del servicio...
                .queryParam("name", "DE"); //... y los parámetros
        LOGGER.info("--- test find product:{}", target.getUri());
        Response response = target.request(MediaType.APPLICATION_JSON).get(); //invocamos al servicio por GET
        if (response.getStatus() != HttpStatus.OK_200.getStatusCode()) { //si no es correcto..
            fail("Error :" + response.getStatusInfo().getReasonPhrase()); //.. lanza el error y termina
        }
        //si todo está bien, tratamos de leer el contenido como la lista de Productos esperado
        List<Product> data = response.readEntity(new GenericType<List<Product>>() {
        });
        Assert.assertFalse(data.isEmpty()); //.. no debería estar vacía la lista.
        data.forEach((p) -> {
            LOGGER.info("\tproduct:{}", p.getName()); //mostramos el contenido.
        });

    }


Notemos lo siguiente:
  1. Línea 63: usaremos la misma data de prueba para buscar los productos.
  2. Línea 66 al 68, crearemos la llamada al URL del servidor preparado en el entorno, y le pasaremos los parámetros. Podríamos haberlo hecho escribiendo los parámetros como un URL que conocemos, así: /webresources/product/?name=DE, pero se ve más elegante utilizando los métodos de esa clase.
  3. Línea 70: es la llamada en sí al servicio. Línea 71: posiblemente haya un error, así que mejor lo atrapamos y mostramos el error.
  4. Línea 75: convertimos el resultado de JSON a una lista de Productos que sí podemos entender.
  5. Línea 77: suponemos que no esté vacía.
  6. Línea 78: Mostramos el contenido.


Al ejecutar la prueba, este es el resultado:



Probando el servicio POST

Ahora, haremos la prueba del método de venta de producto. Recordemos lo siguiente: es de tipo POST, recibe dos parámetros,si está todo bien devuelve un código de error 200 y el monto de la venta, pero si no existe el producto o sobrepasa el stock, recibiremos otros códigos de errores.

Aquí vamos, este es el método:

    @Test
    @UsingDataSet("datasets/products.yml")
    @ShouldMatchDataSet("datasets/after_sales_web.yml")
    public void testSell() throws URISyntaxException {
        Client client = createClient();
        WebTarget target = client.target(deploymentURL.toURI()) //registramos el servidor...
                .path("webresources/sell"); //... y el path del servicio
        LOGGER.info("--- test sell product:{}", target.getUri());

        Form form = new Form() //preparamos  el formulario para el POST
                .param("productId", "2")
                .param("quantity", "2");

        Response response = target.request(MediaType.TEXT_PLAIN)
                .post(Entity.form(form)); //invocamos al servicio

        //procesamos el resultado de la invocacion
        switch (Response.Status.fromStatusCode(response.getStatus())) {
            case OK: //si esta ok (200)
                Double mount = response.readEntity(Double.class); //leemos el valor..
                LOGGER.info("Monto de venta:{}", mount); //.. y lo mostramos.
                break;
            case NOT_FOUND: //si es 404...
                LOGGER.error("El producto no fue encontrado"); //.. no está el producto
                break;
            case BAD_REQUEST: //si es 400..                
                LOGGER.error("El stock no alcanza"); //... está fuera de stock
                break;
            default:
                //en cualquier otro caso, mostrar el error 
                fail("Error :" + response.getStatusInfo().getReasonPhrase());
        }
    }


  1. En la línea 85 se indica que se utilizará la misma data para las pruebas
  2. La línea 86 indica cómo quedará la data después de la venta. Como son otros valores que se están probando, tendrá otro resultado, por eso se ha creado un archivo diferente de resultados.
  3. Cómo los parámetros de tipo POST son diferentes que los GET (el primero es por formulario y el segundo por query), en la línea 93 se configura cómo se enviarán los parámetros al servicio.
  4. La línea 101 procesa la respuesta de la petición utilizando el código HTTP recibido.
  5. Lo demás está claro de entender.


El contenido del archivo after_sales_web.yml es como sigue:

PRODUCT:
- PRODUCT_ID: 1
  PRODUCT_NAME: MESAS
  PRODUCT_PRICE: 150
  PRODUCT_ENABLED: TRUE
  PRODUCT_STOCK: 20
- PRODUCT_ID: 2
  PRODUCT_NAME: JUEGO DE SOFA
  PRODUCT_PRICE: 500
  PRODUCT_ENABLED: TRUE
  PRODUCT_STOCK: 3
- PRODUCT_ID: 3
  PRODUCT_NAME: SILLA DE PLAYA
  PRODUCT_PRICE: 20
  PRODUCT_ENABLED: TRUE
  PRODUCT_STOCK: 100
- PRODUCT_ID: 4
  PRODUCT_NAME: BAUL
  PRODUCT_PRICE: 150
  PRODUCT_ENABLED: FALSE
  PRODUCT_STOCK: 0
SELL:
- MOUNT: 1000
  QUANTITY: 2
  product_id: 2   
  


Al ejecutar la prueba, este es el resultado:

Notar cómo se ejecutan las instrucciones SQL al realizar la venta.

Recursos

Como es costumbre en este post, aquí está disponible el código fuente del proyecto hasta este post:

https://bitbucket.org/apuntesdejava/payara-arquillian/src/

Redes sociales

Twitter

Facebook

Comentarios

Entradas más populares de este blog

UML en NetBeans

Cambiar ícono a un JFrame

RESTful... la forma más ligera de hacer WebServices (Parte 1)