Andrey Redko

About Andrey Redko

Andriy is a well-grounded software developer with more then 12 years of practical experience using Java/EE, C#/.NET, C++, Groovy, Ruby, functional programming (Scala), databases (MySQL, PostreSQL, Oracle) and NoSQL solutions (MongoDB, Redis).

Expressive JAX-RS integration testing with Specs2 and client API 2.0

No doubts, JAX-RS is an outstanding piece of technology. And upcoming specification JAX-RS 2.0 brings even more great features, especially concerning client API. Topic of today’s post is integration testing of the JAX-RS services. There are a bunch of excellent test frameworks like REST-assured to help with that, but the way I would like to present it is by using expressive BDD style. Here is an example of what I mean by that:
 
 
 
 
 

    Create new person with email <a@b.com>
     Given REST client for application deployed at http://localhost:8080
        When I do POST to rest/api/people?email=a@b.com&firstName=Tommy&lastName=Knocker
        Then I expect HTTP code 201

Looks like typical Given/When/Then style of modern BDD frameworks. How close we can get to this on JVM, using statically compiled language? It turns out, very close, thanks to great specs2 test harness.

One thing to mention, specs2 is a Scala framework. Though we are going to write a bit of Scala, we will do it in a very intuitive way, familiar to experienced Java developer. The JAX-RS service under the test is the one we’ve developed in previous post. Here it is:

package com.example.rs;

import java.util.Collection;

import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import com.example.model.Person;
import com.example.services.PeopleService;

@Path( '/people' ) 
public class PeopleRestService {
    @Inject private PeopleService peopleService;

    @Produces( { MediaType.APPLICATION_JSON } )
    @GET
    public Collection< Person > getPeople( @QueryParam( 'page') @DefaultValue( '1' ) final int page ) {
        return peopleService.getPeople( page, 5 );
    }

    @Produces( { MediaType.APPLICATION_JSON } )
    @Path( '/{email}' )
    @GET
    public Person getPeople( @PathParam( 'email' ) final String email ) {
        return peopleService.getByEmail( email );
    }

    @Produces( { MediaType.APPLICATION_JSON  } )
    @POST
    public Response addPerson( @Context final UriInfo uriInfo,
            @FormParam( 'email' ) final String email, 
            @FormParam( 'firstName' ) final String firstName, 
            @FormParam( 'lastName' ) final String lastName ) {

        peopleService.addPerson( email, firstName, lastName );
        return Response.created( uriInfo.getRequestUriBuilder().path( email ).build() ).build();
    }

    @Produces( { MediaType.APPLICATION_JSON  } )
    @Path( '/{email}' )
    @PUT
    public Person updatePerson( @PathParam( 'email' ) final String email, 
            @FormParam( 'firstName' ) final String firstName, 
            @FormParam( 'lastName' )  final String lastName ) {

        final Person person = peopleService.getByEmail( email );  
        if( firstName != null ) {
            person.setFirstName( firstName );
        }

        if( lastName != null ) {
            person.setLastName( lastName );
        }

        return person;     
    }

    @Path( '/{email}' )
    @DELETE
    public Response deletePerson( @PathParam( 'email' ) final String email ) {
        peopleService.removePerson( email );
        return Response.ok().build();
    }
}

Very simple JAX-RS service to manage people. All basic HTTP verbs are present and backed by Java implementation: GET, PUT, POST and DELETE. To be complete, let me also include some methods of the service layer as these ones raise some exceptions of our interest.

package com.example.services;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.stereotype.Service;

import com.example.exceptions.PersonAlreadyExistsException;
import com.example.exceptions.PersonNotFoundException;
import com.example.model.Person;

@Service
public class PeopleService {
    private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); 

    // ...

    public Person getByEmail( final String email ) {
        final Person person = persons.get( email );  

        if( person == null ) {
            throw new PersonNotFoundException( email );
        }

        return person;
    }

    public Person addPerson( final String email, final String firstName, final String lastName ) {
        final Person person = new Person( email );
        person.setFirstName( firstName );
        person.setLastName( lastName );

        if( persons.putIfAbsent( email, person ) != null ) {
            throw new PersonAlreadyExistsException( email );
        }

        return person;
    }

    public void removePerson( final String email ) {
        if( persons.remove( email ) == null ) {
            throw new PersonNotFoundException( email );
        }
    }
}

Very simple but working implementation based on ConcurrentMap. The PersonNotFoundException is being raised in a case when person with requested e-mail doesn’t exist. Respectively, the PersonAlreadyExistsException is being raised in a case when person with requested e-mail already exists. Each of those exceptions have a counterpart among HTTP codes: 404 NOT FOUND and 409 CONFLICT. And it’s the way we are telling JAX-RS about that:

package com.example.exceptions;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class PersonAlreadyExistsException extends WebApplicationException {
    private static final long serialVersionUID = 6817489620338221395L;

    public PersonAlreadyExistsException( final String email ) {
        super(
            Response
                .status( Status.CONFLICT )
                .entity( 'Person already exists: ' + email )
                .build()
        );
    }
}
package com.example.exceptions;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class PersonNotFoundException extends WebApplicationException {
    private static final long serialVersionUID = -2894269137259898072L;

    public PersonNotFoundException( final String email ) {
        super(
            Response
                .status( Status.NOT_FOUND )
                .entity( 'Person not found: ' + email )
                .build()
        );
    }
}

The complete project is hosted on GitHub. Let’s finish with boring part and move on to the sweet one: BDD. Not a surprise that specs2 has a nice support for Given/When/Then style, as described in the documentation. So using specs2, our test case becomes something like this:

'Create new person with email <a@b.com>' ^ br^
    'Given REST client for application deployed at ${http://localhost:8080}' ^ client^
    'When I do POST to ${rest/api/people}' ^ post(
        Map(
            'email' -> 'a@b.com', 
            'firstName' -> 'Tommy', 
            'lastName' -> 'Knocker'
        )
    )^
    'Then I expect HTTP code ${201}'  ^ expectResponseCode^
    'And HTTP header ${Location} to contain ${http://localhost:8080/rest/api/people/a@b.com}' ^ expectResponseHeader^

Not bad, but what are those ^, br, client, post, expectResponseCode and expectResponseHeader? The ^, br is just some sugar specs2 brings to support Given/When/Then chain. Others, post, expectResponseCode and expectResponseHeader are just couple of functions/variables we define to do actual work. For example, client is a new JAX-RS 2.0 client, which we create like that (using Scala syntax):

val client: Given[ Client ] = ( baseUrl: String ) => 
    ClientBuilder.newClient( new ClientConfig().property( 'baseUrl', baseUrl ) )

The baseUrl is taken from Given definition itself, it’s enclosed into ${…} construct. Also, we can see that Given definition has a strong type: Given[ Client ]. Later we will see that same is true for When and Then, they both do have respective strong types When[ T, V ] and Then[ V ].

The flow looks like this:

  • start from Given definition, which returns Client.
  • continue with When definition, which accepts Client from Given and returns Response
  • end up with number of Then definitions, which accept Response from When and check actual expectations

Here is how post definition looks like (which itself is When[ Client, Response ]):

def post( values: Map[ String, Any ] ): When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  
    client
        .target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' )
        .request( MediaType.APPLICATION_JSON )
        .post( 
            Entity.form( values.foldLeft( new Form() )( 
                ( form, param ) => form.param( param._1, param._2.toString ) ) 
            ),
            classOf[ Response ] 
        )

And finally expectResponseCode and expectResponseHeader, which are very similar and have the same type Then[ Response ]:

val expectResponseCode: Then[ Response ] = ( response: Response ) => ( code: String ) => 
    response.getStatus() must_== code.toInt                           

val expectResponseHeader: Then[ Response ] = ( response: Response ) => ( header: String, value: String ) =>        
    response.getHeaderString( header ) should contain( value )

Yet another example, checking response content against JSON payload:

'Retrieve existing person with email <a@b.com>' ^ br^
    'Given REST client for application deployed at ${http://localhost:8080}' ^ client^
    'When I do GET to ${rest/api/people/a@b.com}' ^ get^
    'Then I expect HTTP code ${200}' ^ expectResponseCode^
    'And content to contain ${JSON}' ^ expectResponseContent(
    '''
        {
            'email': 'a@b.com', 
            'firstName': 'Tommy', 
            'lastName': 'Knocker' 
        }            
    '''
    )^

This time we are doing GET request using following get implementation:

val get: When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  
    client
        .target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' )
        .request( MediaType.APPLICATION_JSON )
        .get( classOf[ Response ] )

Though specs2 has rich set of matchers to perform different checks against JSON payloads, I am using spray-json, a lightweight, clean and simple JSON implementation in Scala (it’s true!) and here is the expectResponseContent implementation:

def expectResponseContent( json: String ): Then[ Response ] = ( response: Response ) => ( format: String ) => {
    format match { 
        case 'JSON' => response.readEntity( classOf[ String ] ).asJson must_== json.asJson
        case _ => response.readEntity( classOf[ String ] ) must_== json
    }
}

And the last example (doing POST for existing e-mail):

'Create yet another person with same email <a@b.com>' ^ br^
    'Given REST client for application deployed at ${http://localhost:8080}' ^ client^
    'When I do POST to ${rest/api/people}' ^ post(
        Map( 
            'email' -> 'a@b.com' 
        )
    )^
    'Then I expect HTTP code ${409}' ^ expectResponseCode^
    'And content to contain ${Person already exists: a@b.com}' ^ expectResponseContent^

Looks great! Nice, expressive BDD, using strong types and static compilation! For sure, JUnit integrations is available and works great with Eclipse.

Not to forget about own specs2 reports (generated by maven-specs2-plugin): mvn clean test

Please, look for complete project on GitHub. Also, please note, as I am using the latest JAX-RS 2.0 milestone (final draft), the API may change a bit when be released.
 

Reference: Expressive JAX-RS integration testing with Specs2 and client API 2.0 from our JCG partner Andrey Redko at the Andriy Redko {devmind} blog.

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


three − = 2



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books