Antonio Goncalves is a senior software architect living in Paris. Initially focused on Java development since the late 1990s, his career has taken him to different countries and companies where he works now as a Java EE consultant in software architecture. As a former BEA consultant he has a great expertise in application servers such as Weblogic, JBoss and, of course, GlassFish. He is particularly fond of Open Source and is a member of the OOSGTP (Open Source Get Together Paris). He is also the co-creator of the Paris Java User Group and talks on Les Cast Codeurs podcast. Antonio wrote a first book in French on Java EE 5 back in 2007. Since then he has join the JCP and is an Expert Member of various JSRs (Java EE 6, JPA 2.0 and EJB 3.1). He then published a second book for Apress: Beginning Java EE 6 Platform with GlassFish 3. For the last years Antonio has been talking at international conferences mainly about Java EE, including JavaOne, The Server Side Symposium, Devoxx, Jazoon… He has also written numerous technical papers and articles for IT Web sites (DevX, JaxEnter) or IT magazines (Programmez, Linux Magazine). Antonio is a DZone MVB and is not an employee of DZone and has posted 30 posts at DZone. You can read more from them at their website. View Full User Profile

Test your JAX-RS 2.0 Web Service URIs… Without Mocks

12.27.2012
| 5402 views |
  • submit to reddit

 

After the announcement of the NoMock Movement I had to write another post about integration testing. Here it goes  : how to test your nice RESTful URIs ?

Use Case

Often you hear that URIs have to be expressive… and you want to test that your URIs are nicely written. How do you do that ? Unit testing with mock frameworks such as Restito or Rest assured ? If you do that you’ll be mocking the important part of what you really : a web server. Thanks to integration testing, you can just run your RESTful web service in an in memory web container and check that your URIs are correct (useful when you have many RESTful web services and get lost in your URIs)

JAX-RS 2.0 Client API

To integration-test URIs I will use the new JAX-RS 2.0 Client API and the Jersey implementation. If you haven’t followed what’s been happening, here are some news about JAX-RS. At the time of writing this post JAX-RS 2.0 is nearly out and will be in Java EE 7 by Q2 2013. The client API wasn’t standardized in JAX-RS 1.1 and it’s one of the novelties in 2.0. It allows you to make HTTP requests to your remote RESTful web services easily. It is a fluent request building API (i.e. using the Builder design pattern) that uses a small number of classes and interfaces which are in the javax.ws.rs.client package. The Client interface (obtained with the ClientFactory) is a builder of WebTarget instances. A WebTarget represents a distinct URI from which you can invoke requests on to obtain a Response. From this Response you can check HTTP status, length or cookies but more importantly you can get its content (a.k.a entity, message body or payload) through the Entity class.

With that in mind, here are the lines of code to invoke a GET method on a remote RESTful web service located at http://www.myserver.com/book and return a text/plain value:

Client client = ClientFactory.newClient();
WebTarget target = client.target("http://www.myserver.com/book");
Invocation invocation = target.request(MediaType.TEXT_PLAIN).buildGet();
Response response = invocation.invoke();

Thanks to the builder API and some shortcuts, you can write the same behavior in a single line of code:

Response response = ClientFactory.newClient().target("http://www.myserver.com/book").request(MediaType.TEXT_PLAIN).get();

RESTful Web Service

Let’s start with a simple RESTful web service with only GET methods. As you can see below, this service allows you to get a Customer by login (note the regular expression that only allows lowercase) and by ID (regular expression forces to have digits). Then there are two other methods that allow you to search customers by zip code (query param) or name and surname (matrix param).

@Path("/customer")
@Produces(MediaType.APPLICATION_XML)
public class CustomerRestService {

    @GET
    @Path("{login: [a-z]*}")
    public Response getCustomerByLogin(@PathParam("login") String login) {
        Customer customer = new Customer("John", "Smith", "jsmith@gmail.com", "1234565");
        customer.setLogin(login);
        return Response.ok(customer).build();
    }

    @GET
    @Path("{customerId : \\d+}")
    public Response getCustomerById(@PathParam("customerId") Long id) {
        Customer customer = new Customer("John", "Smith", "jsmith@gmail.com", "1234565");
        customer.setId(id);
        return Response.ok(customer).build();
    }

    @GET
    public Response getCustomersByZipCode(@QueryParam("zip") Long zip) {
        Customers customers = new Customers();
        customers.add(new Customer("John", "Smith", "jsmith@gmail.com", "1234565"));
        customers.add(new Customer("John", "Smith", "jsmith@gmail.com", "1234565"));
        return Response.ok(customers).build();
    }

    @GET
    @Path("search")
    public Response getCustomerByName(@MatrixParam("firstname") String firstname, @MatrixParam("surname") String surname) {
        Customers customers = new Customers();
        customers.add(new Customer("John", "Smith", "jsmith@gmail.com", "1234565"));
        customers.add(new Customer("John", "Smith", "jsmith@gmail.com", "1234565"));
        return Response.ok(customers).build();
    }
}

How to invoke these methods ?

And if you have a URI like /customer/AGONCAL it would be invalid because of the uppercase (the regex only allows lower cases).

Integration-Testing URIs

So let’s test these URIs in an integration test using a real HTTP server.

public class CustomerRestServiceIT {

    @Test
    public void shouldCheckURIs() throws IOException {

        URI uri = UriBuilder.fromUri("http://localhost/").port(8282).build();

        // Create an HTTP server listening at port 8282
        HttpServer server = HttpServer.create(new InetSocketAddress(uri.getPort()), 0);
        // Create a handler wrapping the JAX-RS application
        HttpHandler handler = RuntimeDelegate.getInstance().createEndpoint(new ApplicationConfig(), HttpHandler.class);
        // Map JAX-RS handler to the server root
        server.createContext(uri.getPath(), handler);
        // Start the server
        server.start();

        Client client = ClientFactory.newClient();

        // Valid URIs
        assertEquals(200, client.target("http://localhost:8282/customer/agoncal").request().get().getStatus());
        assertEquals(200, client.target("http://localhost:8282/customer/1234").request().get().getStatus());
        assertEquals(200, client.target("http://localhost:8282/customer?zip=75012").request().get().getStatus());
        assertEquals(200, client.target("http://localhost:8282/customer/search;firstname=John;surname=Smith").request().get().getStatus());

        // Invalid URIs
        assertEquals(404, client.target("http://localhost:8282/customer/AGONCAL").request().get().getStatus());
        assertEquals(404, client.target("http://localhost:8282/customer/dummy/1234").request().get().getStatus());

        // Stop HTTP server
        server.stop(0);
    }
}

The idea is to launch an in-memory HTTP server. Jersey has several extensions so you can use Grizzly or GlassFish. But a very simple test would be to just use the com.sun.net.httpserver.HttpServer that comes with the Oracle JDK. As you can see in line 11, the only thing we need to do is to attach a com.sun.net.httpserver.HttpHandler with the JAX-RS application configuration (class ApplicationConfig not shown here, but you can download the code). Then you just need to start the in memory web server (server.start();), check your valid (return code 200) and invalid (return code 404) URIs and stop the server. That’s it.

I Gave a Quick Try at Unit-Test

I did give a try at unit testing this use case using  Restito. I have to be honest here, I’ve quickly looked at the developer’s guide and after struggling with Maven dependencies (REST Assured, Grizzly, Google Collections…) I managed to “unit test” my use case. I haven’t looked much into it but the logs from Restito looked a bit weired for a unit test :

org.glassfish.grizzly.http.server.NetworkListener start
org.glassfish.grizzly.http.server.HttpServer start
[HttpServer] Started
org.glassfish.grizzly.http.server.NetworkListener stop

So I don’t know if Restito is really starting Grizzly HTTP server of not, but if it is, it’s not really mocking much.

Conclusion

This integration-test will run in a few milliseconds (on my Mac, 3 secondes on a normal Windows XP box) and you will really check your URIs not mocking anything… But of course, this is a very simple test. Most of the time you need database access, injection and so on. That’s when when you bring Arquillian into play ;o) Maybe a topic to write about in a future post.

References


Published at DZone with permission of Antonio Goncalves, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)