Java Web Services with RESTEasy, JSON, TestNG, Mockito & Maven

Thushara Jayasinghe
7 min readMar 14, 2018

--

RESTEasy is a Java framework for REST web service development. It is pretty simple to use where all complexities are hidden from the developer. I couldn't found a complete tutorial which includes unit testing on internet. Hence decided to write this article.

The scope of the tutorial is to develop a sample REST web service & write unit tests for the service. Application will provide a web service interface to search a product by providing product name as a JSON request. We will be using IntelliJ as the IDE, Maven as the build tool, TestNG as the unit testing framework, Mockito as mocking framework & Tomcat as the application server for the example.

  1. Create new maven project

Run below maven archetype command to generate empty web application project.

mvn archetype:generate -DgroupId=com.medium.resteasy -DartifactId=resteasy -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

Once maven project is created open your project in IntelliJ IDE as below.

Folder structure

2. Adding maven dependencies to pom.xml file

Open your pom.xml file and below dependencies. The entire file is shown below.

<?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>com.medium.resteasy</groupId>
<artifactId>resteasy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<!--resteasy-->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.1.4.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>3.0.19.Final</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

3. Create java package structure

Add two sub folder under main folder called java & test. You can do it by right clicking on main folder in IDE. Then you need to configure IDE to recognize these folders as source files and test files as below.

Right click on project, then Open Module settings window (F4) and set Sources & Tests.

Now right click on java/test folders and add new java packages as below.

Package Structure

4. Create RESTEasy application class

You can register the REST service by extending javax.ws.rs.core.Application class.

package com.resteasy.app;
// import the rest service you created!

import com.resteasy.rest.ProductRestService;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class RESTApplication extends Application {
private Set<Object> singletons = new HashSet<Object>();

public RESTApplication() {
singletons.add(new ProductRestService());
}

@Override
public Set<Object> getSingletons() {
return singletons;
}
}

This class should be mentioned in web.xml file which will is shown in next step. You can add many REST services to the singaltons list. We will be writing ProductRestService class in step 6.

5. Adding RESTEasy servlet mapping to web.xml file

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
>
<display-name>Restful Web Application</display-name>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<!--Prefix for the endpoints-->
<param-value>/</param-value>
</context-param>
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.resteasy.app.RESTApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<!--Prefix for endpoint-->
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

You can add a relative context path to your service URL something like http://localhost:8080/resteasy/rest with the parameter resteasy.servlet.mapping.prefix. We kept it as “/” for our example. RESTApplication created in step 4 is mentioned in this file.

6. Create service endpoint for PUT/POST requests

The service endpoint ProductRestService will send/receive JSON requets/responses from the client application. We can map the URL pattern to methods defined in this class using annotations. In our example client will send a JSON POST request to search a Product with the product name & ProductRestService will capture the request. Products are stored in a Map in MyService class. Product is looked up in this Map & search result will be send to client as a JSON response. Mapping of JSON strings to Java Objects is handled by the framework.

Below POJO is used to capture the JSON request.

package com.resteasy.domain;//Mapping POJO for the JSON request
public class ProductSearchRequest {
String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Lets define our ProductRestService.java class to handle requests.

package com.resteasy.rest;

import com.resteasy.domain.ProductSearchRequest;
import com.resteasy.service.MyService;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/")
public class ProductRestService {
//Service to search products
MyService myService = new MyService();

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/product")
public void searchProduct(final ProductSearchRequest productSearchRequest, final @Suspended AsyncResponse asyncResponse) {
//recommended to use java executor framework
new Thread() {
public void run() {
//do some other time consuming work if required
asyncResponse.resume(Response.status(200).entity(getMyService().getProduct(productSearchRequest.getName())).build());
}
}.start();
}

public MyService getMyService() {
return myService;
}
public void setMyService(MyService myService) {
this.myService = myService;
}
}
  • Class level @Path annotation will map the URL context path “/” to ProductRestService.java
  • Method level @Path(“/product”) will map the URL context path “/product” to searchProduct() method
  • @Consumes(MediaType.APPLICATION_JSON)and @Produces(MediaType.APPLICATION_JSON)annotations will allow searchProduct() method to send/receive JSON data
  • ProductSearchRequest.java is the mapping POJO for the JSON request
  • The JAX-RS 2.0 specification has added asynchronous HTTP support via two classes. The @Suspended annotation, and AsyncResponse interface. Asynchronous HTTP Request Processing is a relatively new technique that allows you to process a single HTTP request using non-blocking I/O and, if desired in separate threads.
    Source : https://docs.jboss.org/resteasy/docs/3.0-beta-3/userguide/html/Asynchronous_HTTP_Request_Processing.html

7. Create model object

package com.resteasy.domain;

public class Product {
String name;
int qty;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getQty() {
return qty;
}

public void setQty(int qty) {
this.qty = qty;
}
}

8. Create business service class

This is a dummy class to provide product search service. This class will be mocked using Mockito when writing unit tests. In real world may be the database query service.

package com.resteasy.service;

import com.resteasy.domain.Product;

import java.util.HashMap;
import java.util.Map;

public class MyService {
Map<String, Product> store = new HashMap<String, Product>();

public MyService() {
Product laptop = new Product();
laptop.setName("Laptop");
laptop.setQty(100);

Product tv = new Product();
laptop.setName("TV");
laptop.setQty(50);

store.put("Laptop", laptop);
store.put("TV", tv);
}


public Product getProduct(String name) {
return store.get(name);
}
}

9. Deploy in Tomcat

Apache Tomcat should be installed in your local machine to deploy your application. Go to Edit Configuration dialog in tool bar and add new Tomcat configuration as below.

Tomcat Deployment Configuration
Tomcat Server Configuration

Remember to select the war file that should be deployed under deployment tab.

Set Deployment Artifact

Once deployed application will be running in http://localhost:8080

11. Test your application in Web Browser

It is possible to test our application in browser using REST client addons. There are many addons available for different browsers. I am using an addon called RESTClient in Firefox.

Please set the Content-Type to application/json using Headers menu item. If not you may not be able to send JSON payload.

Define Headers
URL: http://localhost:8080/product
Method : POST
Body : {
"name":"TV"
}
Send POST request
Response : {
"name": "TV",
"qty": 50
}

12. Write unit tests

Unit testing is a must to ensure that your functionality is working as expected. Lets create a class under test package called ProductRestServiceTest.java as below. RESTEasy has inbuilt support to simulate Requests & Responses thru Mock implmentations.

package com.resteasy;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.resteasy.domain.Product;
import com.resteasy.domain.ProductSearchRequest;
import com.resteasy.rest.ProductRestService;
import com.resteasy.service.MyService;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.SynchronousDispatcher;
import org.jboss.resteasy.core.SynchronousExecutionContext;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import java.net.URISyntaxException;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ProductRestServiceTest {
private static Dispatcher dispatcher;
private static ProductRestService productRestService;
private MyService myService;

// This code here gets run before our tests begin
@BeforeClass
public void setup() {
dispatcher = MockDispatcherFactory.createDispatcher();
myService = mock(MyService.class);
productRestService = new ProductRestService();
productRestService.setMyService(myService);

Product product = new Product();
product.setName("TV");
product.setQty(50);
when(myService.getProduct("TV")).thenReturn(product);

dispatcher.getRegistry().addSingletonResource(productRestService);
}

@Test
public void helloTest() throws Exception {
MockHttpResponse response = sendAsyncPostRequest("/product", createRequestJSON("TV"));
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
Assert.assertEquals(createResponseJSON("TV", 50), getPrettyPrintJSON(response.getContentAsString()));
}

public String getPrettyPrintJSON(String input) {
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(input).getAsJsonObject();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(json);
}

public String createRequestJSON(String productName) {
ProductSearchRequest product = new ProductSearchRequest();
product.setName(productName);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(product);
}

public String createResponseJSON(String productName, int qty) {
Product product = new Product();
product.setName(productName);
product.setQty(qty);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(product);
}

public MockHttpResponse sendAsyncPostRequest(
String path,
String requestBody) throws URISyntaxException, JAXBException {

MockHttpRequest request = MockHttpRequest.post(path);
request.accept(MediaType.APPLICATION_JSON);
request.contentType(MediaType.APPLICATION_JSON_TYPE);
request.content(requestBody.getBytes());

MockHttpResponse response = new MockHttpResponse();
SynchronousExecutionContext synchronousExecutionContext = new SynchronousExecutionContext((SynchronousDispatcher) dispatcher, request, response);
request.setAsynchronousContext(synchronousExecutionContext);
return sendHttpRequest(request, response);
}

private MockHttpResponse sendHttpRequest(MockHttpRequest request, MockHttpResponse response) throws URISyntaxException {
dispatcher.invoke(request, response);
return response;
}
}
  • Dispatcher is provided by RESTEasy for mocking servlets
  • ProductService is the class under test
  • We are using Mockito to mock the MyService class
  • MockHttpRequest & MockHttpResponse is provided by RESTEasy
  • Google GSON library is used to convert POJO to JSON and vise versa
  • SynchronousExecutionContext is needed to handle Asynchronous responses.

14. Running unit tests

Yee!! Test is passed.

--

--