Dans son api, Jersey propose la classe JerseyTest qui permet de démarrer un server en mémoire ainsi que quelques méthodes d'aide pour construire ses requêtes.
En réalité, le fait d'être sur AppEngine, ne va rien changer à ce que nous en ferons au sein de cet article.
Un peu de code
Nous allons utiliser une resource toute simple : Hello.
Pour cet article, nous allons commencer par utiliser un type de retour simple lui aussi : du texte brut. Nous disposerons de 2 méthodes, un GET et un POST.
Voici notre ressource :
@Path("hello") @Singleton @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) public class HelloResource { @Context UriInfo uriInfo; @Inject private HelloService helloService; @GET @Path("/{name}") public String reply(@PathParam("name") String name){ return helloService.saysHelloToSomeone(name); } @POST public Response send(String name){ String hello = helloService.sendHello(name); URI uri = uriInfo.getAbsolutePathBuilder().build(); return Response.created(uri).entity(hello).build(); } public void setHelloService(HelloService helloService) { this.helloService = helloService; } }
Je ne vais pas rentrer dans les détails de l’implémentation, ce n'est pas le but de cet article.
Le HelloService utilisé est le suivant :
@Singleton public class HelloService { public String saysHelloToSomeone(String name){ return "Hello "+name; } public String sendHello(String name) { return "Hello "+name; } }
Et pour finir, nous aurons la configuration Guice suivante pour fonctionner correctement avec Jersey :
public class GuiceServletConfig extends GuiceServletContextListener { private static final String SDAAS_SERVER_RESOURCES_PACKAGE = HelloResource.class.getPackage().getName(); private static final String JERSEY_CONFIG_PROPERTY_PACKAGES = "com.sun.jersey.config.property.packages"; @Override protected Injector getInjector() { final Mapparams = new HashMap<String, String>(); params.put(JERSEY_CONFIG_PROPERTY_PACKAGES, SDAAS_SERVER_RESOURCES_PACKAGE); return Guice.createInjector(new ServletModule() { @Override protected void configureServlets() { serve("/*").with(GuiceContainer.class, params); } }); } }
Les ressources exposées sont découvertes automatiquement par l'utilisation du paramètre de clé JERSEY_CONFIG_PROPERTY_PACKAGES. Il leur suffit donc que mes ressources soit dans le package pointé par la valeur de ce paramètre pour qu'elle soit prise en compte.
Et un zeste de test pour garder la fraicheur
Il nous reste plus qu'à mettre en place quelques tests dessus.
Il v falloir créer une classe de test qui hérite de JerseyTest.
Comme je disais, celui ci va lancer son serveur en mémoire, il nécessite donc une dépendance supplémentaire :
<dependency> <groupid>com.sun.jersey.jersey-test-framework</groupid> <artifactid>jersey-test-framework-grizzly</artifactid> <version>${jersey.version}</version> <scope>test</scope> </dependency>
Fonctionnant par un mécanisme spi, il n'aura pas besoin de configuration supplémentaire pour se lancer.
JerseyTest n'utilise pas le web.xml et doit donc être configuré par du code; soit par le constructeur, soit par l'implémentation de la méthode protected AppDescriptor configure(). Nous allons privilégier le second choix. D'autant plus que nous allons devoir utiliser un autre Injector Guice pour pouvoir mocker HelloResource, sans quoi nous ne pourrons pas récupérer l'instance de HelloResource. Un autre avantage est d'isoler la ressource que je souhaite tester.
private static Injector injector; private HelloService helloServiceMock; @Before public void setUp() throws Exception { super.setUp(); HelloResource helloResource = injector.getInstance(HelloResource.class); helloServiceMock = mock(HelloService.class); helloResource.setHelloService(helloServiceMock); } @Override protected AppDescriptor configure() { injector = Guice.createInjector(new ServletModule() { @Override protected void configureServlets() { bind(HelloResource.class); serve("/*").with(GuiceContainer.class); } }); return new WebAppDescriptor.Builder() .contextListenerClass(GuiceTestConfig.class) .filterClass(GuiceFilter.class) .servletPath("/") .build(); } private static class GuiceTestConfig extends GuiceServletContextListener { @Override public Injector getInjector() { return injector; } }
Il n'y a plus qu'à se créer des méthodes de tests
@Test public void shoulReplyHello(){ String name ="Nicolas"; String hello = "Hello "+name; when(helloServiceMock.saysHelloToSomeone(name)).thenReturn(hello); ClientResponse response = resource().path("hello").path(name).get(ClientResponse.class); verify(helloServiceMock).saysHelloToSomeone(name); assertThat(response.getClientResponseStatus()).isEqualTo(Status.OK); assertThat(response.getType()).isEqualTo(MediaType.TEXT_PLAIN_TYPE); assertThat(response.getEntity(String.class)).isEqualTo("Hello Nicolas"); } @Test public void shouldSendHello(){ String name ="Nicolas"; String hello = "Hello "+name; when(helloServiceMock.sendHello(name)).thenReturn(hello); ClientResponse response = resource().path("hello").post(ClientResponse.class,name); verify(helloServiceMock).sendHello(name); assertThat(response.getClientResponseStatus()).isEqualTo(Status.CREATED); assertThat(response.getType()).isEqualTo(MediaType.TEXT_PLAIN_TYPE); assertThat(response.getEntity(String.class)).isEqualTo("Hello Nicolas"); }
Je vérifie que lorsque j'appelle la bonne url, mon résultat correspond à ce que je veux, soit un Hello qui m'est adressé, au bon format et le bon code retour.
Dans le prochain épisode, nous ajouterons une sérialisation JAX-B.
Le code est disponible ici.
Références :
http://blog.iparissa.com/google-app-engine-jax-rs-jersey/