mercredi 30 mars 2011

Du Jersey, du Guice et de l'App Engine 1/3

Jersey et Guice offre son un assemblage intéressant pour exposer un service REST sur App Engine. Mais comment puis je tester convenablement de cocktail ?
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 Map params = 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/

Aucun commentaire:

Enregistrer un commentaire