July 29, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

A Test-Driven Development Stack for Java: Maven, Jetty, Mockito, and JAX-RS

  • December 30, 2009
  • By Jacek Furmankiewicz
  • Send Email »
  • More Articles »

Using JAX-RS (REST) for Integration Tests

My exposure to REST (and its Java implementation JAX-RS) has been fairly recent. However, once you grasp the benefits of REST, it's hard to imagine designing a server-side application without it. By exposing all your major functionality as REST services (with POST, GET, PUT and DELETE delivering the standard CRUD functionality), your whole system becomes wide open for thorough integration testing—not just from Java, but from any development language. Before settling on our Java unit testing stack, we contemplated performing integration testing via simple shell, Python, or Ruby scripts. However, Maven's seamless integration with JUnit made it a winner.

Most of our integration tests utilize JAX-RS client frameworks to perform POST, PUT and DELETE calls (to Create, Update, and Delete data), which we verify via GET (i.e., the "Read" part of CRUD) to ensure they are executed successfully. It's also very easy to test for negative conditions (e.g., invalid or missing data), as you can expect to receive a regular 500 server-side code error (in case of exceptions) or a 204 (No Content) error if an invalid search parameter was passed in a GET request.

JAX-RS (combined with JAXB annotated entities) allows you to serve JSON or XML payloads seamlessly, depending on what the calling client can accept (e.g., JSON for jQuery Ajax requests, XML for browser requests, etc.).

Here's an example of a JPA entity annotated with JAXB:

@Entity @XmlRootElement(name="Person") @XmlAccessorType(XmlAccessType.FIELD)
public class Person {

   @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
   @XmlElement private Integer id;
   @XmlElement private String firstName;
   @XmlElement private String lastName;
   @XmlElement private String email;

           ...getters/setters/etc....
}

Here is a Spring/JAX-RS REST web service to maintain it:

@Service("personService")
@Path(PersonService.PATH)
public class PersonService {
   
   public static final String PATH = "/person";

   private PersonDao dao;
   @Autowired  public void setPersonDao(PersonDao dao) {
      this.dao = dao;
   }
   
   @GET
   public List<Person> findAll() {
      List<Person> people = dao.findAll();
      return (people.size() > 0) ? people : null;
   }
   
   @GET @Path("{id}")
   public Person find(@PathParam("id") Integer id) {
      return dao.findByPrimaryKey(id);
   }

   @POST 
   public Response add(@FormParam("firstName") String firstName,
         @FormParam("lastName") String lastName, 
         @FormParam("email") String email) {
      
      Person person = new Person();
      person.setFirstName(firstName);
      person.setLastName(lastName);
      person.setEmail(email);
      Integer id = dao.save(person);
      
      return Response.created(UriBuilder.fromPath(PATH + "/{0}").build(id)).entity(person).build();
   }
   
   @PUT @Path("/{id}")
   public Response update(@PathParam("id") Integer id, 
         @FormParam("firstName") String firstName,
         @FormParam("lastName") String lastName, 
         @FormParam("email") String email) {
   
      Person person = dao.findByPrimaryKey(id);
      if (person != null) {
         if (firstName != null) {
            person.setFirstName(firstName);
         }
         if (lastName != null){
            person.setLastName(lastName);
         }
         if (email != null) {
            person.setEmail(email);
         }
         dao.update(person);
         return Response.ok(person).build();
      } else {
         return Response.noContent().build();
      }
      
   }
   
   @DELETE 
   public Response deleteAll() {
      dao.deleteAll();
      return Response.ok().build();
   }
   
   @DELETE @Path("/{id}")
   public Response delete(@PathParam("id") Integer id) {
      dao.deleteByPrimaryKey(id);
      return Response.ok().build();
   }
   
}

Code Coverage with Cobertura

Cobertura is a widely used tool for calculating code coverage (i.e., how much of your code is actually covered by your unit tests). The Cobertura Maven plugin makes it trivial to run Cobertura-instrumented code during unit tests (which is very complex to do by hand via Ant). It does not, however, support running during integration tests (yet). Supposedly, Cobertura will deliver support for this in an upcoming version of the plugin.

Code Samples

The accompanying code download for this article includes a simple Maven project that exposes a web service via REST, with examples of both unit tests and integration tests. Just execute mvn test to run unit tests and mvn integration-test to run integration tests. I hope it will serve as a useful blueprint for your future projects.

The TDD Recipe for Java

I hope this has been a useful overview of how you can integrate different open source Java tools into one coherent and productive development stack. The combination of Maven, Jetty, Mockito, and JAX-RS (with Spring to tie it all together) has been incredibly productive for my team, and we plan to use it as the basis of all our future projects.

As an added benefit, exposing all of your server-side functionality via REST (with JSON payloads) allows you to start creating your web UI using modern JavaScript client-side libraries such as jQuery UI, ExtJS, or JavaScriptMVC—thus, allowing you to stop using any Java server-side framework (such as Struts, JSF, or Spring MVC) altogether. But that's a topic for another article.

Code Download

  • TDD4Java.zip
  • About the Author

    Jacek Furmankiewicz is a Senior Java EE and Erlang/OTP Developer at Radialpoint. He has 15 years of professional IT experience and is a Sun Certified Java Programmer (SCJP).
    Tags: TDD, Test Driven Development, Maven, Jetty, JAX-RS



    Page 3 of 3



    Comment and Contribute

     


    (Maximum characters: 1200). You have characters left.

     

     


    Sitemap | Contact Us

    Rocket Fuel