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

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:

[sourcecode language=”java”]
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();
[/sourcecode]

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

[sourcecode language=”java”]
Response response = ClientFactory.newClient().target("http://www.myserver.com/book").request(MediaType.TEXT_PLAIN).get();
[/sourcecode]

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).

[sourcecode language=”java” highlight=”6,7,14,15,22,30,31″]
@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();
}
}
[/sourcecode]

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.

[sourcecode language=”java” highlight=”11,20,21,22,23,26,27″]
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);
}
}
[/sourcecode]

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 :

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

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

6 thoughts on “Test your JAX-RS 2.0 Web Service URIs… Without Mocks

  1. Hey Antonio,

    As you say correctly, the example is a little bit naïve. When you test a real application, complexity of your setup/teardown code raises drastically. Arquillian is [probably] a good tool, but there are many cases when you need to deal with existing server which you can’t/don’t want to modify, or it’s simply just 3rd party API. Even not necessary in Java. That’s where restito helps you.

    Why restito didn’t work… It’s hard to say without seeing the code. However in src/test/java/guide (https://github.com/mkotsur/restito/tree/master/src/test/java/guide) you can find plenty examples which illustrate each
    chapter of the developer guide.

    Restito uses slf4j for logging, so you may need to add your favorite logging framework and slf4j binding to see more verbose output. Will add this into documentation.

  2. Hi,

    Just wanted to point out that REST Assured is actually not a “mock framework” of any kind but is used for end-to-end testing of REST/HTTP services just like the examples that you provide.

  3. Thanks for the post, it’s a nice way to quickly run tests without an appserver.

  4. Any idea why RestEasy implementation does not implement method createEndpoint() ?

  5. Regarding Restito: it is mocking HTTP responses, but to do so it must start HTTP server to reply with what you want it to reply 🙂 Same as Mockito must instationate a class you want to mock.

Leave a Reply