Test your JAX-RS 2.0 Web Service URIs… Without Mocks
Join the DZone community and get the full member experience.
Join For Free
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 ?
- /customer/agoncal invokes the getcustomerbylogin (get method and the path parameter only allows lowercases)
- /customer/1234 invokes getcustomerbyid (get method and a numerical path parameter)
- /customer?zip=75012 invokes getcustomersbyzipcode using queryparam
- /customer/search;firstname=john;surname=smith invokes getcustomerbyname using matrixparam
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, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments