REST with Asynchronous Jersey and RXJava – Part 1

By | November 19, 2016

In this series of articles I will implement a RESTful web service using Spring Boot and Jersey. I will then modify the service as to use asynchronous server-side request processing and RXJava. Load-tests will be made using Gatling before and after the modifications, in order to determine any improvements.

 Note:
The example program in this article does not implement HATEOAS. HATEOAS, while it may be an essential part of RESTful web services, is not important to what I want to show in this article. If you think I commit heresy by calling my service a RESTful web service then please add a mental filter that replaces all occurrences of “REST” with “JSON over HTTP” or stop reading.

The example program in this article is available on GitHub; the “before” branch is the version without asynchronous Jersey and RXJava, the master branch is the version with the both technologies applied.

With that said, let’s get started with the first part in which the basic implementation of the RESTful web service is implemented using Spring Boot and Jersey (JAX-RS).

Operators of the REST service. Photo by Colin M.L. Burnett.

Operator of the REST service or the author of this article after a long coding session. Photo by Colin M.L. Burnett. CC-BY-2.0.

Architecture of the Example Service

The example service consists of four types of classes; domain classes (entities), repositories, services and REST adapters. Each of the four types of classes are stored in a package of their own. The dependencies between the packages are shown in the following diagram:

Package diagram of the example service to be developed in this article.

Package diagram of the example service to be developed in this article.

Thus repositories have dependencies only to the domain, services have dependencies to the repositories and the domain and finally restadapters have dependencies to services and the domain.

The choice of frameworks etc are:

  • Spring 5
    As of writing this article, Spring 5 has not been released yet.
  • Spring Boot 2
    I chose Spring Boot for the development speed.
    As with Spring 5, not released yet.
  • RxJava 2
    Version 2 was just released and I wanted to use the newer RxJava in order to avoid having to re-learn.
  • Jersey (JAX-RS implementation)
    I have worked more with Jersey and like it better than Spring MVC for REST services. In addition I wanted to try out asynchronous request handling with Jersey.
  • Spring Data JPA
    Again, something I have worked with and which I like. I don’t see myself working with JPA without using Spring Data JPA, given the zero-code repositories.
    In addition, customizing the behaviour of Spring Data JPA repositories is fairly easy, as we will see in this article.

Choices related to testing:

  • TestNG
    Very good testing framework that really shines in the area of multi-threaded tests. Alas, no multi-threaded tests implemented in this article, but I do expect them in a close future.
  • RestAssured
    Another very nice testing framework. This one is for testing REST web services. As of writing this article, version 3 has just been released.
  • Unitils Reflection Assert
    This testing-framework implements comparing two objects using reflection. Finally no more code that compares the state of two objects!

Create the Project

The example project was created using Spring Initializr; either in IntelliJ IDEA, your favourite IDE or on the Spring Initializr web-page. I used the following parameters on the Spring Initializr web-page:

Generate a Maven Project with Spring Boot 2.0.0 (SNAPSHOT).

Parameter Value
Group se.ivankrizsan.restexample
Artifact rest-example
Name rest-example
Description REST example with Jersey and RXJava
Package Name se.ivankrizsan.restexample
Packaging Jar
Java Version 1.8
Language Java

Dependencies:

  • Jersey (JAX-RS)
  • JPA
  • HSQLDB

Here is the URL to generate the project using Spring Initializr.

Modify the new project’s pom.xml file to look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>se.ivankrizsan.restexample</groupId>
    <artifactId>rest-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>rest-example</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.BUILD-SNAPSHOT</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring.version>5.0.0.M3</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
        </dependency>

        <!-- Test dependencies. -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.9.13.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>3.0.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.unitils</groupId>
            <artifactId>unitils-core</artifactId>
            <version>3.4.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

We are now ready to start implementing the example project.

Entity Classes

The domain model consists of the classes shown in this class diagram:

Entities in the example program.

Entities in the example program.

Nothing fancy in these classes, since the focus of the example program is on storing and retrieving these entities and not on their behaviour.

Entity Base Class

In addition to the above domain classes, I have used an abstract base class that implements the common properties of an entity which have a long integer id. This base class is implemented like this:

package se.ivankrizsan.restexample.domain;

import javax.persistence.*;

/**
 * Abstract base class for JPA entities that have a long integer id.
 *
 * @author Ivan Krizsan
 */
@MappedSuperclass
public abstract class LongIdEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    protected Long id;

    public Long getId() {
        return id;
    }

    public void setId(final Long inId) {
        id = inId;
    }

    @Override
    public boolean equals(final Object inOtherObject) {
        if (this == inOtherObject) {
            return true;
        }
        if (inOtherObject == null || getClass() != inOtherObject.getClass()) {
            return false;
        }

        final LongIdEntity theOtherEntity = (LongIdEntity) inOtherObject;

        return id != null ? id.equals(theOtherEntity.id) : theOtherEntity.id == null;

    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

Nothing special about this class. The equals and hashCode methods were generated by the IDE. The reason for implementing this entity base class is, as will be shown later, to be able to create common base classes for services and REST resources.

Shape Class

The Shape class is an abstract entity class that will serve as parent-class for circle and rectangle entities. I find it interesting because it shows how to handle persistence and JSON marshalling and unmarshalling of entity-classes that has a common ancestor.

package se.ivankrizsan.restexample.domain;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import java.awt.*;

/**
 * Abstract base class for shapes in a drawing.
 *
 * @author Ivan Krizsan
 */
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
/* Need to include type information since there are collections that contain shapes. */
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "shapeType")
public abstract class Shape extends LongIdEntity {
    /* Constant(s): */

    /* Instance variable(s): */
    @Column(name = "colour", nullable = false)
    protected String colour;
    @Column(name = "position", nullable = false)
    protected Point position;

    public String getColour() {
        return colour;
    }

    public void setColour(final String inColour) {
        colour = inColour;
    }

    public Point getPosition() {
        return position;
    }

    public void setPosition(final Point inPosition) {
        position = inPosition;
    }
}

Note that:

  • The inheritance strategy is to create one table per concrete subclass of the Shape class.
    Thus there will be one table for circles and another for rectangles.
  • In order to be able to re-create an object of the appropriate type when unmarshalling JSON representation, I had to include the @JsonTypeInfo annotation.
    In the @JsonTypeInfo annotation the three properties use, include and property are used. The use property is configured as to use a minimal class name, for instance Circle or Rectangle, as a subclass discriminator. It does expose some properties (i.e. class names) in generated JSON representation and thus may not be optimal as far as security is concerned.
    The include property is configured to add a property to the JSON representation which name (shapeType) is specified by the property property.

The JSON representation of a circle entity, that is a subclass of Shape, may look like this:

{
    "shapeType": ".Circle",
    "id": 5,
    "colour": "Colour39",
    "position": {
        "x": 585.0,
        "y": 780.0
    },
    "radius": 390
}

Circle Class

The first of the concrete shapes is the circle, which adds a radius property.

package se.ivankrizsan.restexample.domain;

import javax.persistence.Column;
import javax.persistence.Entity;

/**
 * Represents a circle shape with a given radius.
 *
 * @author Ivan Krizsan
 */
@Entity(name = "Circle")
public class Circle extends Shape {
    public final static int DEFAULT_RADIUS = 10;

    @Column(name = "radius", nullable = false)
    protected int radius = DEFAULT_RADIUS;

    public Circle() {
    }

    /**
     * Creates a circle with the supplied radius.
     *
     * @param inRadius Radius of the new circle.
     */
    public Circle(final int inRadius) {
        radius = inRadius;
    }

    public int getRadius() {
        return radius;
    }

    public void setRadius(final int inRadius) {
        radius = inRadius;
    }
}

While I would have wished that I could omit the default constructor in order to prevent creating anything but immutable instances, JPA needs it when instantiating entities.

Rectangle Class

The second, and last, concrete shape is the rectangle.

package se.ivankrizsan.restexample.domain;

import javax.persistence.Column;
import javax.persistence.Entity;

/**
 * Represents a rectangle shape with a given height and width.
 *
 * @author Ivan Krizsan
 */
@Entity(name = "Rectangle")
public class Rectangle extends Shape {
    public final static int DEFAULT_WIDTH = 10;
    public final static int DEFAULT_HEIGHT = 10;

    @Column(name = "height", nullable = false)
    protected int height = DEFAULT_HEIGHT;
    @Column(name = "width", nullable = false)
    protected int width = DEFAULT_WIDTH;

    public Rectangle() {
    }

    /**
     * Creates a rectangle with the supplied height and width.
     *
     * @param inHeight Height of new rectangle.
     * @param inWidth Width of new rectangle.
     */
    public Rectangle(final int inHeight, final int inWidth) {
        height = inHeight;
        width = inWidth;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(final int inHeight) {
        height = inHeight;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(final int inWidth) {
        width = inWidth;
    }
}

Drawing Class

In this very simplistic example, a drawing consists of an arbitrary number of shapes. In addition it has a name and a date on which it was created:

package se.ivankrizsan.restexample.domain;

import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * A drawing that consists of an arbitrary number of shapes.
 *
 * @author Ivan Krizsan
 */
@Entity(name = "Drawing")
@Table(name = "Drawings")
public class Drawing extends LongIdEntity {
    /* Constant(s): */

    /* Instance variable(s): */
    @Column(name = "name", nullable = false)
    protected String name;
    @Column(name = "creationDate", nullable = false)
    protected Date creationDate;
    @OneToMany(fetch = FetchType.EAGER, orphanRemoval = true,
        cascade = CascadeType.ALL)
    @JoinTable(
        name = "DrawingShapes",
        joinColumns = {@JoinColumn(name = "drawing_id")},
        inverseJoinColumns = {@JoinColumn(name = "shape_id")})
    protected Set<Shape> shapes = new HashSet<>();

    /**
     * Adds the supplied shape to the drawing.
     *
     * @param inShape Shape to add to drawing.
     */
    public void addShape(final Shape inShape) {
        shapes.add(inShape);
    }

    public String getName() {
        return name;
    }

    public void setName(final String inName) {
        name = inName;
    }

    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(final Date inCreationDate) {
        creationDate = inCreationDate;
    }

    public Set<Shape> getShapes() {
        return shapes;
    }

    public void setShapes(final Set<Shape> inShapes) {
        shapes = inShapes;
    }
}

Note that:

  • The shapes of a drawing are stored in the shapes set.
    A drawing may contain many shapes and the shapes are owned by the drawing, meaning that they will be deleted if the drawing is deleted.
    A join-table is used to form associations between drawings and the shapes of drawings.

Spring Data JPA Repositories and Customization

The Spring Data JPA repositories would have been trivial if it weren’t for the fact that I want to merge entities with an id into the JPA context if they have been persisted earlier in a way not supported out of the box by Spring Data JPA.
One way of accomplishing this is, as in this example, implementing a custom method to be added to all Spring Data JPA repositories.

Adding a Custom Method to Spring Data JPA Repositories

Adding a custom method to the Spring Data JPA repositories of an application involves the following steps:

  • Create an interface that extends, for instance, JpaRepository and add the custom method to the interface.
  • Create an implementation class that extends SimpleJpaRepository and that implements the interface created in the previous step.
  • In the @EnableJpaRepositories annotation use the repositoryBaseClass element to specify the custom implementation class from the previous step.

Create the Custom Repository Interface

The custom repository interface contains but one single method, which I have named persist.

package se.ivankrizsan.restexample.repositories.customisation;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.repository.NoRepositoryBean;

import javax.persistence.LockModeType;

/**
 * Interface defining custom method(s) added to all the Spring Data JPA repositories in the application.
 *
 * @param <T> Entity type.
 * @author Ivan Krizsan
 */
@NoRepositoryBean
public interface JpaRepositoryCustomisations<T> extends JpaRepository<T, Long> {
    /**
     * Persists the supplied entity.
     * If the entity has an id and previously has been persisted, it will be merged to the
     * persistence context otherwise it will be inserted into the persistence context.
     *
     * @param inEntity Entity to persist.
     * @return Persisted entity.
     */
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    T persist(final T inEntity);
}

Note that:

  • The interface is annotated with the @NoRepositoryBean annotation.
    This was done to prevent Spring Data JPA from believing this interface is a repository interface.
  • The interface extends JpaRepository.
    There are other interfaces in Spring Data JPA that could be used as parent for this interface but I chose JpaRepository since it is the one with most features and the example program is indeed using JPA.
  • The database lock mode on the persist method is pessimistic write.
    The database lock mode should be chosen according to the needs of your application. I recommend having a look at the JavaDoc in the javax.persistence.LockModeType enumeration for more information.

Create the Custom Repository Implementation

The implementation of the custom repository base consists of two constructors and an implementation of the persist method.

package se.ivankrizsan.restexample.repositories.customisation;

import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import se.ivankrizsan.restexample.domain.LongIdEntity;

import javax.persistence.EntityManager;

/**
 * This class implements the Spring Data JPA repository customisations.
 * Need to annotate the persist method in this class with the Spring
 * annotation @Transactional since the superclass {code SimpleJpaRepository}
 * is annotated with @Transactional that sets transactions to read-only
 * for all methods.
 *
 * @param <T> Entity type.
 * @author Ivan Krizsan
 */
public class JpaRepositoryCustomisationsImpl<T> extends SimpleJpaRepository<T, Long> implements
    JpaRepositoryCustomisations<T> {
    /* Constant(s): */

    /* Instance variable(s): */
    protected EntityManager mEntityManager;

    /**
     * Creates a repository instance for the entity specified by the supplied entity
     * information that uses the supplied entity manager.
     * Constructor from superclass.
     *
     * @param inEntityInformation Entity information.
     * @param inEntityManager Entity manager.
     */
    public JpaRepositoryCustomisationsImpl(final JpaEntityInformation<T, ?> inEntityInformation,
        final EntityManager inEntityManager) {
        super(inEntityInformation, inEntityManager);
        mEntityManager = inEntityManager;
    }

    /**
     * Creates a repository instance for the supplied entity type that uses the
     * supplied entity manager.
     * Constructor from superclass.
     *
     * @param inEntityType Entity type.
     * @param inEntityManager Entity manager.
     */
    public JpaRepositoryCustomisationsImpl(final Class<T> inEntityType, final EntityManager inEntityManager) {
        super(inEntityType, inEntityManager);
        mEntityManager = inEntityManager;
    }

    @Transactional
    @Override
    public T persist(final T inEntity) {
        T theSavedEntity = inEntity;
        final Long theEntityId = ((LongIdEntity) theSavedEntity).getId();
        if ((theEntityId != null) && exists(theEntityId)) {
            theSavedEntity = mEntityManager.merge(inEntity);
        } else {
            mEntityManager.persist(inEntity);
        }
        mEntityManager.flush();

        return theSavedEntity;
    }
}

Note that:

  • The class extends the SimpleJpaRepository class.
    This is the default implementation of a JPA repository provided by Spring Data JPA.
  • The class implements the JpaRepositoryCustomisations interface.
    This is the interface we created in earlier that defines the additions to Spring Data JPA repositories we want to make.
  • There is an instance variable of the type EntityManager.
    The custom repository method need to use the entity manager, so it will be saved in this instance variable when an instance of a repository is created.
    The superclass does contain an instance variable of the same type, but it is private and thus not accessible by child classes.
  • There are two constructors.
    Both constructors are from the superclass. In both constructors the entity manager is saved, for the reason described above.
  • The persist method is annotated with the @Transactional annotation.
    This was done to set any transaction that is created when invoking this method to not be read-only, which is the default set in the superclass SimpleJpaRepository.
  • There is an implementation of the persist method.
    Finally, there is an implementation of our custom repository method.

Configure the Repository Base Class

In order for the custom Spring Data JPA repository implementation to be used, we have to add the @EnableJpaRepositories annotation with the repositoryBaseClass element to the test classes and application class like in the examples below.

Unit-test class with the @EnableJpaRepositories annotation added:

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableJpaRepositories(basePackages = {"se.ivankrizsan.restexample.repositories"},
    repositoryBaseClass = JpaRepositoryCustomisationsImpl.class)
public class RestExampleApplicationTests {

Spring Boot main application class with the @EnableJpaRepositories annotation added:

@SpringBootApplication
@EntityScan(basePackages = {"se.ivankrizsan.restexample.domain"})
@EnableAsync
@EnableJpaRepositories(basePackages = {"se.ivankrizsan.restexample.repositories"},
    repositoryBaseClass = JpaRepositoryCustomisationsImpl.class)
public class RestExampleApplication {

Repositories

There are three repository interfaces in the example application, all of which are trivial. Below is the circle repository interface shown. Please refer to the repositories package in the GitHub repository for the other interfaces.

package se.ivankrizsan.restexample.repositories;

import se.ivankrizsan.restexample.domain.Circle;
import se.ivankrizsan.restexample.repositories.customisation.JpaRepositoryCustomisations;

/**
 * Spring Data JPA mRepository for circles.
 *
 * @author Ivan Krizsan
 */
public interface CircleRepository extends JpaRepositoryCustomisations<Circle> {
}

All three repository interfaces extend the custom repository interface we created earlier.

Services

Entities and repositories will be implemented the same way regardless of whether we use asynchronous Jersey and/or RXJava. The next step is the service layer, in which we later will see differences when RxJava is introduced.

Synchronous Service Base Class

All the services in the service layer of the example application have one common super-class, which implements basic functionality of the services.

package se.ivankrizsan.restexample.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import se.ivankrizsan.restexample.domain.LongIdEntity;
import se.ivankrizsan.restexample.repositories.customisation.JpaRepositoryCustomisations;

import java.util.List;

/**
 * Abstract base class for services that has operations for creating, reading,
 * updating and deleting entities.
 * Synchronous version.
 *
 * @param <E> Entity type.
 * @author Ivan Krizsan
 */
@Transactional
public abstract class AbstractServiceBasePlain<E extends LongIdEntity> {
    /* Constant(s): */

    /* Instance variable(s): */
    protected JpaRepositoryCustomisations<E> mRepository;

    /**
     * Creates a mService instance that will use the supplied repository for
     * entity persistence.
     *
     * @param inRepository Entity repository.
     */
    public AbstractServiceBasePlain(final JpaRepositoryCustomisations<E> inRepository) {
        mRepository = inRepository;
    }

    /**
     * Saves the supplied entity.
     *
     * @param inEntity Entity to save.
     * @return Saved entity.
     */
    public E save(final E inEntity) {
        final E theSavedEntity = mRepository.save(inEntity);
        return theSavedEntity;
    }

    /**
     * Updates the supplied entity.
     *
     * @param inEntity Entity to update.
     * @return Updated entity.
     */
    public E update(final E inEntity) {
        final E theUpdatedEntity = mRepository.persist(inEntity);
        return theUpdatedEntity;
    }

    /**
     * Finds the entity having supplied id.
     *
     * @param inEntityId Id of entity to retrieve.
     * @return Found entity, or null if no entity is found.
     */
    @Transactional(readOnly = true)
    public E find(final Long inEntityId) {
        final E theEntity = mRepository.findOne(inEntityId);
        return theEntity;
    }

    /**
     * Finds all the entities.
     *
     * @return List of entities.
     */
    @Transactional(readOnly = true)
    public List<E> findAll() {
        final List<E> theEntitiesList = mRepository.findAll();
        return theEntitiesList;
    }

    /**
     * Deletes the entity having supplied id.
     *
     * @param inId Id of entity to delete.
     */
    public void delete(final Long inId) {
        mRepository.delete(inId);
    }

    /**
     * Deletes all entities.
     */
    public void deleteAll() {
        mRepository.deleteAll();
    }
}

Note that:

  • Java generics are used to determine the type of entity the service is to operate on.
    The entity type E must be a subtype of the entity base class LongIdEntity created earlier.
  • The class is annotated with the @Transactional annotation.
    All methods in service classes that inherit from this class will be executed in a transaction. The transaction boundary has been located in the service classes which may be used by different adapters of the application.
  • There is an instance variable of the type JpaRepositoryCustomisations.
    Using this type is how the customised Spring Data JPA repositories of the example application can commonly be referred to.
  • The class contains methods to support the common CRUD operations.
  • The find and findAll methods are annotated with the @Transactional(readOnly = true) annotation.
    This is a hint to the transaction system that no write or update operations will occur when these methods are invoked.

Concrete Service Classes

With the service base class in place, implementing a basic service becomes trivial, like in the example here showing the circle service:

package se.ivankrizsan.restexample.services;

import org.springframework.stereotype.Service;
import se.ivankrizsan.restexample.domain.Circle;
import se.ivankrizsan.restexample.repositories.CircleRepository;

/**
 * Service exposing operations on circles.
 *
 * @author Ivan Krizsan
 */
@Service
public class CircleService extends AbstractServiceBasePlain<Circle> {

    /**
     * Creates a mService instance that will use the supplied repository
     * for entity persistence.
     *
     * @param inRepository Circle repository.
     */
    public CircleService(final CircleRepository inRepository) {
        super(inRepository);
    }
}

The rectangle and drawing services are equally simple, so I have omitted them. Please refer to the services package in the GitHub repository for implementation details.

REST Resources

Let’s step up another layer. The REST resources exposes the services in a RESTful way over HTTP. As before, the HATEOAS part has been deliberately left out.

Jersey Configuration Class

Before the resource classes are implemented, a little Jersey configuration is necessary.

package se.ivankrizsan.restexample.restadapter;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

/**
 * Jersey configuration.
 *
 * @author Ivan Krizsan
 */
@Component
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        /* All resource classes are to be located in the same package as this class. */
        packages(this.getClass().getPackage().getName());
    }
}

The above configuration class inherits from the Jersey ResourceConfig class, which represents a configurable JAX-RS context. Resource classes are not registered individually, instead we tell Jersey in which package to look for resource classes. The package is the same package as in which the Jersey configuration class is located, so if the resource classes are moved to another package, the Jersey configuration will have to follow or be modified.

REST Resources Base Class

As with the services, there is a common base-class for the REST resources which implement basic CRUD operations for an entity:

package se.ivankrizsan.restexample.restadapter;

import se.ivankrizsan.restexample.domain.LongIdEntity;
import se.ivankrizsan.restexample.services.AbstractServiceBasePlain;

import javax.validation.constraints.NotNull;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.function.Supplier;

/**
 * Abstract base class for REST resources exposing operations on an entity type.
 * All operations will return HTTP status 500 with a plain text body containing an
 * error message if an error occurred during request processing.
 *
 * @param <E> Entity type.
 * @author Ivan Krizsan
 */
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
@Consumes({MediaType.APPLICATION_JSON})
public abstract class RestResourceBasePlain<E extends LongIdEntity> {
    /* Constant(s): */

    /* Instance variable(s): */
    protected AbstractServiceBasePlain<E> mService;


    /**
     * Retrieves all entities.
     *
     * @return HTTP response object with HTTP status 200 if operation succeeded or
     * HTTP error status code and a plain-text error message if an error occurred.
     */
    @GET
    public Response getAll() {
        return performServiceOperation(
            () -> {
                final List<E> theEntitiesList = mService.findAll();
                final E[] theEntitiesArray = entityListToArray(theEntitiesList);
                return Response.ok().entity(theEntitiesArray).build();
            },
            500, "An error occurred retrieving all entities: "
        );
    }

    /**
     * Deletes the entity with supplied id.
     *
     * @param inEntityId Id of entity to delete.
     * @return HTTP response object with HTTP status 200 if operation succeeded or
     * HTTP error status code and a plain-text error message if an error occurred.
     */
    @DELETE
    @Path("{id}")
    public Response deleteEntityById(@PathParam("id") @NotNull final Long inEntityId) {
        return performServiceOperation(
            () -> {
                mService.delete(inEntityId);
                return Response.ok().build();
            },
            500, "An error occurred deleting entity with id " + inEntityId + ": "
        );
    }

    /**
     * Deletes all entities.
     * Will return HTTP status 500 if error occurred during request processing.
     *
     * @return HTTP response object with HTTP status 200 if operation succeeded or
     * HTTP error status code and a plain-text error message if an error occurred.
     */
    @DELETE
    public Response deleteAllEntities() {
        return performServiceOperation(
            () -> {
                mService.deleteAll();
                return Response.ok().build();
            },
            500, "An error occurred deleting all entities: "
        );
    }

    /**
     * Retrieves entity with supplied id.
     *
     * @param inEntityId Id of entity to retrieve.
     * @return HTTP response object with HTTP status 200 if operation succeeded or
     * HTTP error status code and a plain-text error message if an error occurred.
     */
    @GET
    @Path("{id}")
    public Response getEntityById(@PathParam("id") Long inEntityId) {
        return performServiceOperation(
            () -> {
                final E theEntity = mService.find(inEntityId);
                return Response.ok(theEntity).build();
            },
            500, "An error occurred finding entity with id " + inEntityId + ": "
        );
    }

    /**
     * Updates the entity with supplied id by overwriting it with the supplied entity.
     *
     * @param inEntity Entity data to write.
     * @param inEntityId Id of entity to update.
     * @return HTTP response object with HTTP status 200 if operation succeeded or
     * HTTP error status code and a plain-text error message if an error occurred.
     */
    @PUT
    @Path("{id}")
    public Response updateEntity(final E inEntity, @PathParam("id") @NotNull final Long inEntityId) {
        final Response theResponse = performServiceOperation(
            () -> {
                inEntity.setId(inEntityId);
                final E theEntity = mService.update(inEntity);
                return Response.ok(theEntity).build();
            },
            500, "An error occurred updating entity with id " + inEntityId + ": "
        );
        return theResponse;
    }

    /**
     * Creates a new entity using the supplied entity data.
     *
     * @param inEntity Entity data to use when creating new entity.
     * @return HTTP response object with HTTP status 200 containing entity representation
     * if operation succeeded or HTTP error status code and a plain-text error message
     * if an error occurred.
     */
    @POST
    public Response createEntity(final E inEntity) {
        return performServiceOperation(
            () -> {
                Response theResponse;
                if (inEntity.getId() != null) {
                    theResponse = Response.status(400).entity("Id must not be set on new entity").build();
                } else {
                    final E theEntity = mService.save(inEntity);
                    theResponse = Response.ok(theEntity).build();
                }
                return theResponse;
            },
            500, "An error occurred creating a new entity: "
        );
    }

    /**
     * Performs the operation as defined by the supplied response supplier.
     * If the operation completes without errors, the response being the result of the operation
     * is returned as result of this method.
     * If an error occurs during the operation, an error response is created with the
     * supplied HTTP error status and the supplied plain-text error message with the message
     * from the exception that occurred appended.
     *
     * @param inResponseSupplier Operation to complete.
     * @param inErrorHttpStatus HTTP status to set in response in case of error.
     * @param inErrorMessage Plain-text error message to set in response in case of error.
     * @return Response object.
     */
    protected static Response performServiceOperation(final Supplier<Response> inResponseSupplier,
        final int inErrorHttpStatus, final String inErrorMessage) {
        Response theResponse;
        try {
            theResponse = inResponseSupplier.get();
        } catch (final Throwable theException) {
            theResponse = Response.
                status(inErrorHttpStatus).
                entity(inErrorMessage + theException.getMessage()).
                type(MediaType.TEXT_PLAIN_TYPE).
                build();
        }
        return theResponse;
    }

    /**
     * Creates an array containing the entities in the supplied list.
     *
     * @param inEntityList List of entities.
     * @return Array containing the entities from the list.
     */
    protected abstract E[] entityListToArray(final List<E> inEntityList);

    public AbstractServiceBasePlain<E> getService() {
        return mService;
    }

    public void setService(final AbstractServiceBasePlain<E> inService) {
        mService = inService;
    }
}

Note that:

  • The class is annotated with the @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) annotation.
    That a REST resource produces JSON is quite natural. The reason for also producing plain text is that in the case of errors, error messages will be returned in plain text.
  • There is no @Transactional annotation on the class.
    As you can recall from the comments on the service base class, the transaction boundary of the REST service is to be located in the service layer.
  • Similar to the service base class, Java generics are used to determine the type of entity the REST resources is to expose operations on.
    The entity type E must be a subtype of the entity base class LongIdEntity created earlier.
  • There is an instance variable named mService of the type AbstractServiceBasePlain<E>.
    This instance variable will hold the reference to the service used by the resource.
  • There is a method named performServiceOperation.
    This method takes three parameters; a lambda expression (of the type Supplier<Response>), an integer and a string. The lambda expression is evaluated. If no exception is thrown, the result is the Response object returned by the lambda expression, otherwise a new Response object is created in which the HTTP status code is set to the integer parameter and the payload is set to the string with the message from the exception appended.
    The purpose of this method is to perform operations in the REST resource classes of the application in a uniform manner and also to make the code cleaner..
  • In the performServiceOperation method, the type of the response payload created when exceptions occur is plain text.
  • There is an abstract method named entityListToArray.
    This method is supposed to take a list of entities and return an array of entities.
    First of all, this method is to be used when returning multiple entities, since I have encountered some problems when creating JSON representation from a list. The second reason for this method is that I haven’t found a way to create an array of entities from a list of entities when using generics. Thus it will be each subclass’ responsibility to implement this method.

Concrete Resource Classes

With the above abstract resource base class in place, the concrete resource classes are simple to implement. The example below show the circle resource class. Please refer to the restadapter package in the GitHub repository for the resource classes not shown here.

package se.ivankrizsan.restexample.restadapter;

import org.springframework.stereotype.Component;
import se.ivankrizsan.restexample.domain.Circle;
import se.ivankrizsan.restexample.services.CircleService;

import javax.ws.rs.Path;
import java.util.List;

/**
 * REST resource exposing operations on circles.
 *
 * @author Ivan Krizsan
 */
@Component
@Path(CircleResource.PATH)
public class CircleResource extends RestResourceBasePlain<Circle> {
    /* Constant(s): */
    public final static String PATH = "/circles";

    /**
     * Creates a REST resource using the supplied service to manipulate entities.
     *
     * @param inService Service used to manipulate entities.
     */
    public CircleResource(final CircleService inService) {
        setService(inService);
    }

    @Override
    protected Circle[] entityListToArray(final List<Circle> inEntityList) {
        return inEntityList.toArray(new Circle[inEntityList.size()]);
    }
}

Note that:

  • The concrete resource class is annotated with @Component.
    This is in order for Jersey to be able to recognize the resource class when scanning the package we specified earlier in the Jersey configuration class.
  • A constant have been used as the parameter to the @Path annotation.
    This enables me to use the same constant in tests and thus have a “single source to the truth” as far as the resource path is concerned.
  • The constructor takes an instance of the circle service as a parameter.
    Spring will automatically discover the parameter when instantiating the resource class and inject the proper dependency. The reference to the service will be store in the superclass instance variable seen earlier.
    An alternative to constructor injection would have been to introduce an instance variable of the type CircleService in the concrete resource class and then call the setter in the superclass. However, I feel that having two instance variables that in effect refer to the same thing increases the risk for bugs.
  • There is an implementation of the abstract method entityListToArray from the super-class.
    Simple to do once the concrete types are available.

Tests

To paraphrase the lyrics from a song: A man ain’t no man if he ain’t got no tests.
The example program will have tests but before the actual tests are implemented, a few helpers are needed.

JSON Converter

This class is an old friend of mine – I use it over and over when I have to marshal and unmarshal JSON data. It uses the Jackson library.

package se.ivankrizsan.restexample.helpers;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;

import java.io.IOException;

/**
 * Helper methods that creates JSON representation from objects and vice versa.
 *
 * @author Ivan Krizsan
 */
public final class JsonConverter {
    /**
     * Default constructor.
     * Made private to prevent instantiation of this helper class.
     */
    private JsonConverter() {
    }

    /**
     * Creates a JSON representation of supplied object(s).
     *
     * @param inObjectToSerialize Object to create JSON representation from.
     * @return JSON representation of supplied object.
     * @throws Exception If error occurs creating JSON representation.
     */
    public static String objectToJson(final Object inObjectToSerialize)
        throws Exception {
        final ObjectMapper theJsonObjectMapper = createAndConfigureJsonObjectMapper();
        final ObjectWriter theJsonObjectWriter = theJsonObjectMapper.writer();
        final String theJsonString =
            theJsonObjectWriter.writeValueAsString(inObjectToSerialize);
        return theJsonString;
    }

    /**
     * Creates an object of supplied type from supplied JSON string.
     *
     * @param inJsonRepresentation JSON representation from which to create object(s).
     * @param inDestinationType Type of the (root) object to create.
     * @return Object(s) created from JSON representation.
     * @throws IOException If error occurs creating object(s).
     */
    public static <T> T jsonToObject(final String inJsonRepresentation,
        final Class<T> inDestinationType)
        throws IOException {
        final ObjectMapper theJsonObjectMapper = createAndConfigureJsonObjectMapper();
        final ObjectReader theJsonObjectReader = theJsonObjectMapper.readerFor(inDestinationType);
        return theJsonObjectReader.readValue(inJsonRepresentation);
    }

    /**
     * Creates and configures the object mapper used when converting between
     * JSON representation and objects.
     *
     * @return Jackson object mapper.
     */
    private static ObjectMapper createAndConfigureJsonObjectMapper() {
        final ObjectMapper theJsonObjectMapper = new ObjectMapper();
        /* Do not include properties which value is null in JSON representation. */
        theJsonObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return theJsonObjectMapper;
    }
}

Note that:

  • In the method createAndConfigureJsonObjectMapper an object mapper is created and configured which will not include representation for properties that are null when creating a JSON representation.
    While not strictly necessary to omit properties with null values, I have chosen to do so.

Entity Factories

Many are the times when I have thought about the mess that I myself have caused in my test classes. One significant source for this mess have often been code that creates objects or hierarchies of objects to be used in the tests. My latest attempt in this area is to create a factory class in the test code for each type of object that I want to create during tests.

All the entity factories implement one and the same interface:

package se.ivankrizsan.restexample.helpers;

/**
 * Interface that defines the properties of an entity factory that is used in tests
 * to create instances of entities.
 * The id of entities created by an entity factory is never set.
 *
 * @author Ivan Krizsan
 */
public interface EntityFactory<E> {
    /**
     * Creates a new entity instance setting all its properties.
     * The supplied index may, if appropriate, be used to compose values when
     * setting the entity properties.
     *
     * @param inIndex Index for property values.
     * @return New entity instance.
     */
    E createEntity(final int inIndex);
}

In this example program, I have implemented three entity factories:

  • A circle entity factory.
  • A rectangle entity factory.
  • A drawing entity factory.

The drawing entity factory uses the circle and the rectangle entity factories to create shapes.

I will not show the implementation of these entity factories here. Please refer to the GitHub repository for details.

Repository Customisations Test

Since there are Spring Data JPA customisations, as described earlier, I feel it is natural to test these customisations:

package se.ivankrizsan.restexample.repositories;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import se.ivankrizsan.restexample.domain.Circle;
import se.ivankrizsan.restexample.helpers.CircleEntityFactory;
import se.ivankrizsan.restexample.repositories.customisation.JpaRepositoryCustomisationsImpl;

/**
 * Tests the JPA repository customisations implemented in
 * {@link JpaRepositoryCustomisationsImpl}.
 *
 * @author Ivan Krizsan
 */
@SpringBootTest
@EnableJpaRepositories(basePackages = {"se.ivankrizsan.restexample.repositories"},
    repositoryBaseClass = JpaRepositoryCustomisationsImpl.class)
public class RepositoryCustomisationsTest extends AbstractTestNGSpringContextTests {
    /* Constant(s): */
    public final static String UPDATED_COLOUR = "Black 2000";

    /* Instance variable(s): */
    @Autowired
    protected CircleRepository mRepository;
    protected CircleEntityFactory mEntityFactory;
    protected Circle mEntity;

    /**
     * Performs preparations before each test method.
     */
    @BeforeMethod
    public void prepareBeforeTest() {
        mEntityFactory = new CircleEntityFactory();

        final int theCreateEntityIndex = (int) Math.round(Math.random() * 100);
        mEntity = mEntityFactory.createEntity(theCreateEntityIndex);
    }

    /**
     * Tests persisting an entity that has not previously been persisted.
     * Expected outcome: The entity should be persisted and assigned an id.
     *
     * @throws Exception If error occurs. Indicates test failure.
     */
    @Test
    public void testPersistNewEntity() throws Exception {
        Circle theCircle = mRepository.persist(mEntity);
        Assert.assertNotNull(theCircle, "The entity should have been persisted");
        Assert.assertNotNull(theCircle.getId(),
            "The entity should have been assigned an id");
    }

    /**
     * Tests saving an entity that has not previously been persisted.
     * Expected outcome: The entity should be persisted and assigned an id.
     *
     * @throws Exception If error occurs. Indicates test failure.
     */
    @Test
    public void testUpdatePersistedEntity() throws Exception {
        final Circle theCircle = mRepository.persist(mEntity);
        Assert.assertNotNull(theCircle, "The entity should have been persisted");
        Assert.assertNotNull(theCircle.getId(),
            "The entity should have been assigned an id");

        final Circle theSameCircle = mRepository.findOne(theCircle.getId());
        theSameCircle.setColour(UPDATED_COLOUR);
        mRepository.persist(theSameCircle);
        final Circle theUpdatedCircle = mRepository.findOne(theCircle.getId());
        Assert.assertNotNull(theUpdatedCircle, "The updated entity should exist");
        Assert.assertEquals(theUpdatedCircle.getColour(), UPDATED_COLOUR,
            "The property in the entity should have been updated");
    }
}

Note that:

  • The class is annotated with the @SpringBootTest annotation.
    This annotation is used on tests, both JUnit and TestNG, in Spring Boot applications.
  • The class is annotated with the @EnableJpaRepositories annotation.
    As in the section on Spring Data JPA repository customisations earlier, this annotation is required to actually use the custom repository implementation.
  • The test class extends the class AbstractTestNGSpringContextTests.
    Since this test is intended to be a TestNG unit-test, the test class must extend this base-class when developing a Spring-based application.
  • Testing is performed on the circle repository.
    The Spring Data JPA customisations in this example are applied to all the repositories, so I just chose one repository at random to test on.

REST Resources Tests

When testing REST resource classes, I have used a strategy similar to what was seen with the service classes; an abstract base-class that uses Java generics that is later subclassed for each REST resource class to test. This base-class can be extended for a specific resource type and will then provide a basic set of tests for the resource type in question. Tests that are specific to a certain resource type may be added to the subclass as desired.

REST Resources Test Base Class

The abstract REST resource test base class looks like this:

package se.ivankrizsan.restexample.restadapter;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import org.unitils.reflectionassert.ReflectionAssert;
import se.ivankrizsan.restexample.domain.LongIdEntity;
import se.ivankrizsan.restexample.helpers.EntityFactory;
import se.ivankrizsan.restexample.helpers.JsonConverter;
import se.ivankrizsan.restexample.repositories.customisation.JpaRepositoryCustomisationsImpl;

import java.io.IOException;

/**
 * Abstract base class for tests of REST resources.
 * Only JSON representation is used in the tests.
 *
 * @author Ivan Krizsan
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@EnableJpaRepositories(basePackages = {"se.ivankrizsan.restexample.repositories"},
    repositoryBaseClass = JpaRepositoryCustomisationsImpl.class)
public abstract class RestResourceTestBase<E extends LongIdEntity> extends
    AbstractTestNGSpringContextTests {
    /* Constant(s): */
    protected static final int ENDPOINT_PORT = 8080;
    protected static final long TEST_TIMEOUT = 30000;

    /* Instance variable(s): */
    protected EntityFactory<E> mEntityFactory;
    protected CrudRepository<E, Long> mEntityRepository;
    protected String mResourceUrlPath;
    protected E mExpectedEntity;
    protected int mCreateEntityIndex;

    /**
     * Sets up RestAssured test framework before tests.
     */
    @BeforeTest
    public void setUpRestAssured() {
        RestAssured.reset();
        RestAssured.port = ENDPOINT_PORT;
        RestAssured.basePath = "";
    }

    /**
     * Performs preparations before each test method.
     * Creates and persists one entity before running each test method.
     */
    @BeforeMethod
    public void prepareBeforeTest() {
        mCreateEntityIndex = (int) Math.round(Math.random() * 100);
        final E theEntity = mEntityFactory.createEntity(mCreateEntityIndex);
        mExpectedEntity = mEntityRepository.save(theEntity);

        Assert.assertNotNull(mExpectedEntity);
        Assert.assertNotNull(mExpectedEntity.getId());
    }

    /**
     * Tests retrieving one entity.
     * An entity should be retrieved and the properties of the entity should have the
     * same values as the entity persisted before the test.
     *
     * @throws IOException If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testGetEntity() throws IOException {
        final Response theResponse = RestAssured.
            given().
            contentType("application/json").
            accept("application/json").
            when().
            get(mResourceUrlPath + "/" + mExpectedEntity.getId());
        final String theResponseJson = theResponse.prettyPrint();
        theResponse.
            then().
            statusCode(200).
            contentType(ContentType.JSON);

        final Object theRetrievedEntity = JsonConverter.jsonToObject(
            theResponseJson, mExpectedEntity.getClass());
        ReflectionAssert.assertLenientEquals(
            "Retrieved entity should have the correct property values",
            mExpectedEntity, theRetrievedEntity);
    }

    /**
     * Tests deletion of one entity.
     * This test does not verify deletion of contained entities
     * to which the delete operation is to be cascaded.
     * The entity should have been deleted.
     *
     * @throws IOException If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testDeleteEntity() throws IOException {
        RestAssured.
            given().
            when().
            delete(mResourceUrlPath + "/" + mExpectedEntity.getId()).
            then().
            statusCode(200);

        final E thePersistedEntityAfterDelete =
            mEntityRepository.findOne(mExpectedEntity.getId());
        Assert.assertNull(thePersistedEntityAfterDelete,
            "Entity should have been deleted");
    }

    /**
     * Tests deletion of all entities.
     * This test does not verify deletion of contained entities
     * to which the delete operation is to be cascaded.
     * All entities should have been deleted.
     *
     * @throws IOException If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testDeleteAllEntities() throws IOException {
        RestAssured.
            given().
            when().
            delete(mResourceUrlPath).
            then().
            statusCode(200);

        final Iterable<E> thePersistedEntitiesAfterDelete =
            mEntityRepository.findAll();
        Assert.assertFalse(thePersistedEntitiesAfterDelete.iterator().hasNext(),
            "All entities should have been deleted");
    }

    /**
     * Tests creation of one entity.
     * An entity should be created and the properties of the entity should have the
     * same values as the properties in the entity representation sent to
     * the service.
     *
     * @throws Exception If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testCreateEntity() throws Exception {
        mEntityRepository.deleteAll();
        final E theExpectedEntity = mEntityFactory.createEntity(1);
        final String theJsonRepresentation =
            JsonConverter.objectToJson(theExpectedEntity);
        final Response theResponse = RestAssured.
            given().
            contentType("application/json").
            accept("application/json").
            body(theJsonRepresentation).
            when().
            post(mResourceUrlPath);
        final String theCreatedEntityJson = theResponse.prettyPrint();
        theResponse.
            then().
            statusCode(200).
            contentType(ContentType.JSON);

        final LongIdEntity theCreatedEntity =
            JsonConverter.jsonToObject(
                theCreatedEntityJson, theExpectedEntity.getClass());
        /*
         * Id will be null in new entity, need to set id so comparision
         * do not fail due to this.
         */
        theExpectedEntity.setId(theCreatedEntity.getId());
        ReflectionAssert.assertLenientEquals(
            "Created entity should have the correct property values",
            theExpectedEntity, theCreatedEntity);
    }

    /**
     * Tests updating one entity.
     * An updated entity should be returned.
     *
     * @throws Exception If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testUpdateEntity() throws Exception {
        final Long theExistingEntityId = mExpectedEntity.getId();
        final E theExpectedEntity =
            mEntityFactory.createEntity(mCreateEntityIndex + 1);
        theExpectedEntity.setId(theExistingEntityId);
        final String theJsonRepresentation =
            JsonConverter.objectToJson(theExpectedEntity);
        final Response theResponse = RestAssured.
            given().
            contentType("application/json").
            accept("application/json").
            body(theJsonRepresentation).
            when().
            put(mResourceUrlPath + "/" + mExpectedEntity.getId());
        final String theResponseJson = theResponse.prettyPrint();
        theResponse.
            then().
            statusCode(200).
            contentType(ContentType.JSON);

        final Object theUpdatedEntity = JsonConverter.jsonToObject(
            theResponseJson, mExpectedEntity.getClass());
        ReflectionAssert.assertLenientEquals(
            "Updated entity should have the correct property values",
            theExpectedEntity, theUpdatedEntity);
    }

    /**
     * Tests updating an entity that has not previously been persisted.
     * The update should fail and no entity should be persisted.
     *
     * @throws Exception If error occurs. Indicates test failure.
     */
    @Test(timeOut = TEST_TIMEOUT)
    public void testUpdateEntityNotPersisted() throws Exception {
        final long theEntityCountBefore = mEntityRepository.count();

        final E theExpectedEntity =
            mEntityFactory.createEntity(mCreateEntityIndex + 1);
        final String theJsonRepresentation =
            JsonConverter.objectToJson(theExpectedEntity);
        final Response theResponse = RestAssured.
            given().
            contentType("application/json").
            accept("application/json").
            body(theJsonRepresentation).
            when().
            put(mResourceUrlPath + "/" + mExpectedEntity.getId() + 1);

        theResponse.
            then().
            statusCode(500).
            contentType(ContentType.TEXT);

        final long theEntityCountAfter = mEntityRepository.count();
        Assert.assertEquals(theEntityCountAfter, theEntityCountBefore,
            "Number of entities should be unchanged");
    }
}

Note that:

  • The class is annotated with the @SpringBootTest annotation.
    As before, this annotation is used on tests, both JUnit and TestNG, in Spring Boot applications. In addition there is the webEnvironment element with the value SpringBootTest.WebEnvironment.DEFINED_PORT. This will cause tests inheriting from this class to run in a real, embedded, web container (as opposed to a mock servlet environment) using the defined port (as opposed to a random port).
  • The class is annotated with the @EnableJpaRepositories annotation.
    As in the section on Spring Data JPA repository customisations earlier, this annotation is required to use the custom repository implementation.
  • The test class extends the class AbstractTestNGSpringContextTests.
    As before, this is required with TestNG-based test classes.
  • Java generics are used to determine the type of entity the resource exposes operations on.
    The entity type E must be a subtype of the entity base class LongIdEntity created earlier.
  • There is a constant, ENDPOINT_PORT, that determines to which port resource requests over HTTP will be sent to.
  • There is a constant, TEST_TIMEOUT, which determine the time in milliseconds within which a test must complete, or else it will fail due to timeout.
  • The instance variable mEntityFactory is to hold a reference to an instance of one of the entity factories we developed earlier.
    The entity factory will be used to create instances of entities in the tests.
  • The instance variable mEntityRepository holds a reference to a repository for the entity type related to the tested REST resource.
    The repository is used in the tests to verify that the requests to the REST resource gives the desired result.
  • The instance variable mResourceUrlPath is to contain the path for the REST resource that is to be tested.
    For example, in the case of the REST resource operating on rectangles, this will be “/rectangles”, which is the value of the constant RectangleResource.PATH seen earlier.
  • The instance variable mExpectedEntity will contain a reference to an entity which has already been persisted.
    The tests will try to retrieve, delete and update this entity using requests to the REST resource.
  • The instance variable mCreateEntityIndex will contain the seed value used when creating the expected entity mentioned above.
    It is saved in order for tests to be able to create an entity using a seed value that has not been used.
  • There is a method setUpRestAssured annotated with the @BeforeTest annotation.
    This method will be executed once before the tests in a concrete test class are run. It prepares the RestAssured test framework.
  • There is a method prepareBeforeTest which is annotated with the @BeforeMethod annotation.
    This method will be invoked once before each test method is executed. In this base-class, this method creates and persists an entity. This method will be overridden by subclasses, as we soon will see, in order to perform subclass-specific initialization.
  • In the testGetEntity, testCreateEntity and testUpdateEntity test methods, an assertion named assertLenientEquals is used.
    This an Unitils assertion which, using Java reflection, compares the values of each field in the two objects. Lenient assertion means that the order of items in a collection or array is not significant and that Java default values for fields, such as null, zero or false, will be ignored when comparing two objects.

Concrete Resource Tests

As an example of a concrete REST resource test-class, we will have a look at the circle resource test.

package se.ivankrizsan.restexample.restadapter;

import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.BeforeMethod;
import se.ivankrizsan.restexample.domain.Circle;
import se.ivankrizsan.restexample.helpers.CircleEntityFactory;
import se.ivankrizsan.restexample.repositories.CircleRepository;

/**
 * Tests the {@code CircleResource}.
 *
 * @author Ivan Krizsan
 */
public class CircleResourceTest extends RestResourceTestBase<Circle> {
    /* Constant(s): */

    /* Instance variable(s): */
    @Autowired
    protected CircleRepository mCircleRepository;

    @BeforeMethod
    @Override
    public void prepareBeforeTest() {
        mEntityFactory = new CircleEntityFactory();
        mEntityRepository = mCircleRepository;
        mResourceUrlPath = CircleResource.PATH;

        super.prepareBeforeTest();
    }
}

We can see that:

  • A reference to the circle repository is to be autowired into the instance variable mCircleRepository.
    The reference in this instance variable is later copied to the parent-class’ instance variable mEntityRepository seen earlier.
  • The instance variables mEntityFactory and mResourceUrlPath are set up as appropriate for the circle entity type.
  • The superclass’ method prepareBeforeTest is invoked.
    It is important to invoke this method and to invoke it only after all the instance variables of the parent-class have been set up properly.

The rectangle and drawing resource tests are very similar, so I will not show them here. They are available in the example program repository.

Final Words Part One

You should be able to run all the tests in the example program without any failures.
In the next article in this series we will look at how to load-test the example REST service using Gatling.

Happy coding!

2 thoughts on “REST with Asynchronous Jersey and RXJava – Part 1

  1. Oleh

    Thank you, Ivan. I really enjoyed these series about the performance testing. Really cool blog! Keep rocking’ mate

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *