Testing the JAX-RS RESTful Web Service

Posted by | No Tags | Software developement | No Comments on Testing the JAX-RS RESTful Web Service

If we wish to create RESTful Web Service in Java, then the  JAX-RS  is the officially accepted Java API for this. This is also referred to as the lightweight little brother of JAX-WS  (Java API for XML Web Services). At the moment the JAX-RS 2.0 version is the latest (JSR 339 standard), it was released in May 2013. The JAX-RS defines the RESTful API on the server side, and since the JAX-RS 2.0, it has also contained a client API, which is very useful for testing. Some of the most important JAX-RS implementations are the following:


For testing the JAX-RS, an available service is needed on the server side, and the client calling it. In case of automated tests it is useful if the test itself ensures that the service is available. To achieve this, in the test a server needs to be started and the JAX-RS service that is to be tested must be installed on it. In this case we are talking about an integration test. In our case the test launched a Glassfish, and we installed the JAX-RS web application to be tested on it, with the help of Arquillian. Once the service is available, we can perform the test on the client side, embedded in a JUnit  test.

Bookstore test case

For our example, take a simple bookstore application, where we can request books according to their title. The server now accepts POST requests, and the format of the request and answer shall be JSON or XML. Thus the server-side part of the JAX-RS code is similar to the following:

@Path("book")
public class BookService {

  BookDao bookDao;

  @Path("by_title")
  @POST
  @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  public Book getBook(BookRequest request) {
    return bookDao.getBookByTitle(request.getTitle());
  }

}

The bookstore contains the following books:

Id Title Author Price
1 War and peace Leo Tolstoy 49
2 East of Eden John Steinbeck 59

Testing with JAX-RS client API

As I have mentioned, the JAX-RS 2.0 contains client API as well, so first we write our test client with the help of this. The client API can be found under the javax.ws.rs.client package. For a REST call, we first create a Client with the ClientBuilder. After this, we aim at the WebTarget identified by URI with the client, and the WebTarget is then parameterized, and we send the request on it, in case of POST, wrapped, for example, in an Entity. We only need to read the answer from the Response into a Book object. The Book and BookRequest are simple POJOs, only provided with @XmlRootElement annotation due to XML/JSON serialization. For the serialization we have to provide an XML/JSON provider, in this case it is JacksonJaxbJsonProvider.


import com.wcs.rest.model.Book;
import com.wcs.rest.model.BookRequest;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
import org.junit.Test;
import static org.junit.Assert.*;

public class BookTest {

    @Test
    public void testBookByTitle() {
        BookRequest request = new BookRequest();
        request.setTitle("East of Eden");

        Entity entity = Entity.json(request);

        Client client = createClient();

        WebTarget target = client
                .target("http://localhost:8080/rest-service/rest/book/by_title");

        Response response = target
                .request(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .post(entity);

        Book book = response.readEntity(Book.class);

        assertEquals(Status.OK.getStatusCode(), response.getStatus());
        assertEquals(2, book.getId());
        assertEquals("John Steinbeck", book.getAuthor());
        assertEquals(59, book.getPrice());
    }

    Client createClient() {
        return ClientBuilder
                .newBuilder()
                .register(JacksonJaxbJsonProvider.class)
                .build();
    }
}

In the code everything looks simple from the API side, however, to make it work, on the client side one of the JAX-RS Client API implementations must be used as well. On the server side, the JAX-RS implementation does not normally mean any problems, as, in ideal cases, it is already part of the server. For instance, here we can choose from the above-mentioned three implementations. In case of a Maven client application, the following dependencies belong to the respective implementations:

In case of Jersey, the dependencies were the following:

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.4.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>2.4.1</version>
            <scope>test</scope>
        </dependency>

The dependencies used in case of RESTEasy:

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson-provider</artifactId>
            <version>3.0.6.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.0.6.Final</version>
            <scope>test</scope>
        </dependency>

The used dependencies in case of Apache CXF:

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-rs-client</artifactId>
            <version>3.0.0-milestone1</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.13</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-xc</artifactId>
            <version>1.9.12</version>
        </dependency>

Testing with the help of REST-Assured

If we don’t want to use the JAX-RS Client API, REST-Assured is a good alternative. This provides an easy-to-use and easy-to-read API for testing:

import static com.jayway.restassured.RestAssured.*;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.response.Response;
import com.wcs.rest.model.Book;
import com.wcs.rest.model.BookRequest;
import javax.ws.rs.core.Response.Status;
import static org.junit.Assert.*;
import org.junit.Test;

public class BookTest {

    @Test
    public void testGetBookByTitle() {
        BookRequest request = new BookRequest();
        request.setTitle("War and peace");

        Response response = given()
                                .body(request)
                                .contentType(ContentType.JSON)
                            .expect()
                                .contentType(ContentType.JSON)
                                .statusCode(Status.OK.getStatusCode())
                            .when()
                                .post("http://localhost:8080/rest-service/rest/book/by_title");

        Book book = response.as(Book.class);

        assertEquals(1, book.getId());
        assertEquals("Leo Tolstoy", book.getAuthor());
    }
}

The logging of the REST calls is well solved in REST-Assured. Firstly, logging can be easily configured both in case of request and response. Secondly, a nicely formatted JSON and XML can be seen in the log. These are very useful for testing. In the following code section we logged everything concerning the request, and only the body in case of the response.

given()
  .body(request)
  .contentType(ContentType.JSON)
  .log().all()
.expect()
  .contentType(ContentType.JSON)
  .statusCode(Status.OK.getStatusCode())
  .log().body()
.when()
  .post("http://localhost:8080/rest-service/rest/book/by_title");

Integration test with the help of Arquillian

As I have mentioned, in case of automated tests it is necessary that the test itself should install the JAX-RS service that is to be tested, before the test calls run. The examples presented so far did not provide this. Now I will briefly present how such integration tests can be created with the help of Arquillian. I will not elaborate on the detailed parameterization of Arquillian (e.g. arquillian.xml, Maven dependencies) here, as the documentation about it can for instance be found here. The code of the integration test is the following:

import static com.jayway.restassured.RestAssured.*;
import com.jayway.restassured.http.ContentType;
import com.wcs.rest.dao.BookDao;
import com.wcs.rest.model.Book;
import com.wcs.rest.model.BookRequest;
import com.wcs.rest.service.BookService;
import java.net.URI;
import java.net.URL;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class BookIT {

    @ArquillianResource
    URL deploymentUrl;

    @Deployment
    public static WebArchive create() {
        return ShrinkWrap.create(WebArchive.class, "rest-service.war")
                .addClasses(BookService.class,
                        BookDao.class,
                        Book.class,
                        BookRequest.class,
                        ...); // classes and other resources into the war
    }

    @Test
    public void testGetBookByTitle() {
        given()
            .body(new BookRequest("War and peace"))
            .contentType(ContentType.JSON)
        .expect()
            .contentType(ContentType.JSON)
            .statusCode(Status.OK.getStatusCode())
        .when()
            .post(buildUri("rest", "book", "by_title"));
    }

    URI buildUri(String... paths) {
        UriBuilder builder = UriBuilder.fromUri(deploymentUrl.toString());
        for (String path : paths) {
            builder.path(path);
        }
        return builder.build();
    }
}

At first sight this is a simple JUnit test. What makes it an integration test is that by using the @RunWith(Arquillian.class) annotation, we order the test to be run with Arquillian. In the method provided with the @Deployment annotation we assemble the web application to be installed (WAR). The classes, JARs and other resources (e.g. web.xml) used by the application must be inserted into the Shrinkwrap WebArchive. Arquillian will install this WAR in the container (e.g. Glassfish or other Java EE application server) set in the application (arquillian.xml) The JUnit tests will run after this installation has been completed. It is also worth to mention the URL provided with @ArquillianResource annotation. Here Arquillian will insert the URL of the constructed application, thus we do not need to burn it in the tests. This is useful, because if we would like to run our tests with several different containers, we do not have to struggle with arranging that they use the same host, ip, etc. settings, as the correct URL is inserted in this variable while running.


No Comments

Leave a comment