Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The Art of Java-Based HTTP Data Service Implementation and Testing (Part 2)

DZone's Guide to

The Art of Java-Based HTTP Data Service Implementation and Testing (Part 2)

Want to see how to incorporate end-to-end testing for your Java HTTP data services? Take a look at out ActFramework and some associated tools can help.

· Java Zone ·
Free Resource

Verify, standardize, and correct the Big 4 + more– name, email, phone and global addresses – try our Data Quality APIs now at Melissa Developer Portal!

In the previous article of this HTTP data service implementation and testing series, we walked through the process of creating a simple data service in Java with a built-in fully automated testing facility. In today's post, we will flesh out the project with RESTful service endpoints for a simple TODO task management system. We will also demonstrate how to create automated testing cases for all service endpoints. Here are the service endpoints we will code in today's journey:

  • GET /todos
    Get all TODO items
  • GET /todos/?q=?
    Query TODO items with the description of the value passed in by "q"
  • GET /todos/{id}
    Return the TODO item specified by ID
  • POST /todos
    Add one TODO item
  • DELETE /todos/{id}
    Remove the TODO item specified by ID

So let's start by creating our project. Typing the following command in your console to generate the project:

mvn archetype:generate -B \
    -DgroupId=demo.todo \
    -DartifactId=todo-service \
    -DarchetypeGroupId=org.actframework \
    -DarchetypeArtifactId=archetype-simple-restful-service \
    -DarchetypeVersion=1.8.8.5


Now open the project in your IDE (we recommend using IntelliJ IDEA (Community Edition) for all coding exercises in this post). Once you are done, you should be able to see something like this:

Image title

Open the "pom.xml" file in your IDE and add the dependency of the database access plugin:

<dependency>
    <groupId>org.actframework</groupId>
    <artifactId>act-eclipselink</artifactId>
</dependency>


Here, we are using act-eclipselink, which is backed by EclipseLink, a JPA library to provide database access capabilties for our TODO service. It can be drop replaced with act-hibernate. No code needs to change with this dependency update.

The Server class is not used in the TODO application, so just remove that file and add the Model class Todo into the project:

package demo.todo;

import act.util.SimpleBean;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name = "todo")
public class Todo implements SimpleBean {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Integer id;
    public String desc;
}


Here we get Todo to implement the SimpleBean so we can declare all fields as public, and ActFramework will generate the Getter/Setter methods for the Todo class. And when it invokes something like todo.desc = "Task A" outside of the Todo class, the invocation will be replaced with todo.setDesc("Task A"); and the invocation of String s = todo.desc will be replaced with String s = todo.getDesc();

The next class we need to add into the project is our RESTful service TodoService:

package demo.todo;

import act.controller.annotation.UrlContext;
import act.db.jpa.JPADao;
import act.util.JsonView;
import org.osgl.mvc.annotation.DeleteAction;
import org.osgl.mvc.annotation.GetAction;
import org.osgl.mvc.annotation.PostAction;
import org.osgl.util.S;

import java.util.List;
import javax.inject.Inject;

@JsonView
@UrlContext("/todos")
public class TodoService {

    @Inject
    private JPADao<Integer, Todo> dao;

    @GetAction
    public List<Todo> list(String q) {
        return S.blank(q) ?
                dao.findAllAsList() :
                dao.q("desc like", q).fetch();
    }

    @GetAction("{id}")
    public Todo findById(int id) {
        return dao.findById(id);
    }

    @PostAction
    public int create(Todo todo) {
        dao.save(todo);
        return todo.id;
    }

    @DeleteAction("{id}")
    public void remove(int id) {
        dao.deleteById(id);
    }

}


Now we can start the app and try our TODO service endpoints. You can either launch the app in the IDE or via mvn command.

To launch the app in IDEA, open the AppEntry class and follow the following screenshot:

Image title

To launch the app in a console, type the following mvn command:

mvn clean compile act:run 


After the app has been launched, we can test it via HTTP connection tool, e.g. Postman. But here in this article, I will use a simple console tool named httpie to test our service endpoints.

First, create a TODO item:

luog@luog-X510UQR:~$ http POST localhost:5460/todos desc='Task A'
HTTP/1.1 201 Created
Content-Length: 12
Content-Type: application/json
Date: Wed, 30 May 2018 07:40:58 GMT
Server: act/1.8.8-RC8

{
    "result": 1
}


From the console log, we see the service returned successfully with the ID of the new TODO item.

Now that we have a record in the database, we can test the service endpoint that returns all TODO items in the system:

luog@luog-X510UQR:~$ http localhost:5460/todos
HTTP/1.1 200 OK
Content-Length: 26
Content-Type: application/json
Date: Wed, 30 May 2018 07:41:53 GMT
Server: act/1.8.8-RC8

[
    {
        "desc": "Task A", 
        "id": 1
    }
]


Get the exact TODO item with the ID:

luog@luog-X510UQR:~$ http localhost:5460/todos/1
HTTP/1.1 200 OK
Content-Length: 24
Content-Type: application/json
Date: Wed, 30 May 2018 07:42:28 GMT
Server: act/1.8.8-RC8

{
    "desc": "Task A", 
    "id": 1
}


Delete the TODO item by ID and verify the delete option:

luog@luog-X510UQR:~$ http DELETE localhost:5460/todos/1
HTTP/1.1 204 No Content
Content-Type: application/json
Date: Wed, 30 May 2018 07:44:53 GMT
Server: act/1.8.8-RC8



luog@luog-X510UQR:~$ http localhost:5460/todos
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Date: Wed, 30 May 2018 07:44:59 GMT
Server: act/1.8.8-RC8

[]


Looks great! We're done with the implementation, and the manual testing shows the functions are all good.

Now we are going to implement fully automated end-to-end testing fo our TODO application. The tool we are using is act-e2e, a plugin designed to enable easy implementation of en-to-end testing for ActFramework applications. Here is the feature list of act-e2e:

  1. End-to-end tests. Meaning tests are done by sending requests to the application via HTTP channel and verifying the response sent back by the application. The test is completely isolated from the application implementation.
  2. Test cases are defined in YAML files and are super easy to read and write. It just needs to define the request and the rules to verify the response — no need to understand the internal data structure of the application.
  3. Test environment management, including:
    • Clear data before starting a test scenario
    • Load test data before starting a test scenario
      • The test data is defined in a YAML file called fixtures.
  4. Other tools
    1. Request template: It might need to define common attributes for all requests, in which case a request template can be defined and referenced by other requests.
    2. Last response data cache: Sometimes, it might need to refer to the responded data of the last interaction (e.g. ID of a newly created record) to construct the request of the next iteration.

The Scenario file (normally in src/main/resources/e2e/scenarios.yml) is the center of e2e tests. The structure of the file can be depicted as:

Image title

We are not going to enumerate all syntax and semantics of the Scenario file used in act-e2e. Instead, we will take the journey of creating test scenarios for our TODO application. It should review a lot of things in e2e Scenario files in an easy way.

Now let's open the src/main/resources/e2e/scenarios.yml file and remove its old content, which is created for the removed Service class. Then let's add a scenario with our first interaction for our TODO app:

Scenario(Main):
  description: Test TODO service
  interactions:
    - description: Add one todo item
      request:
        method: POST
        url: /todos
        params:
          desc: Task A
      response:
        json:
          result:
            - exists: true


It's pretty easy to tell the first iteration is created for TODO create a service endpoint test. The request part is fairly straightforward and lets us put some notes about the response part. So the json part means the response shall be JSON data, and it must have a field named result. To understand the rationale behind that, let's get back to our TODO item and create some service endpoint code:


@PostAction
@Transactional
public int create(Todo todo) {
    dao.save(todo);
    return todo.id;
}


Initially, it doesn't look correct, as the service endpoint actually returns an integer, so what happened with that result? The reason is because we have the @JsonView annotation on the TodoService class, which dictates the framework to generate valid JSON responses for each request with returning data. Since the primitive type value doesn't have our structure, we will use "result" to wrap it, generating something like {"result": 1} as the response, and that's the reason we create the response verification spec above.

Since we have the first iteration created, we can use the /~/e2e endpoints provided by the act-e2e plugin to test our scenarios file. Open the browser and navigate to http://localhost:5460/~/e2e, it should display something like:

Image title

Meaning our first run passed, which is good. Let's move ahead to our next iteration — copy the following code to your scenarios.yml file:

    - description: Fetch todo item added in last interaction
      request:
        method: GET
        url: /todos/${last:result}
      response:
        json:
          desc: Task A


This time, the request deserves a little bit of an explanation. So the aim is to send the request to fetch the TODO item created in the last iteration. The URL should be /todos/{id}, where "id" should be the value of the result field of the JSON data returned in the last response, so we use the special notation ${last:result} to fetch the value in place. The response is pretty simple, it expects a JSON response with desc to be "Task A", exactly what we put in the last interaction to create the item.

Refresh the browser and we should get something like:

Image title

The next iteration is to get a TODO list. Add the following code into your scenarios.yml file:

    - description: Fetch todo item list
      request:
        method: GET
        url: /todos
      response:
        json:
          size: 1
          0:
            desc: Task A


The response spec of this one is a bit interesting. We want to verify the data looks like:

HTTP/1.1 200 OK
Content-Length: 26
Content-Type: application/json
Date: Wed, 30 May 2018 07:41:53 GMT
Server: act/1.8.8-RC8

[
    {
        "desc": "Task A", 
        "id": 1
    }
]


In our response spec, we specified the data should be JSON, and the size: 1 means if it is a JSON array, then it shall contain one element, and the 0 part specifies the verification rules for the first element in the array, which shall have a desc property be "Task A".

Now refresh the browser it should show that all three interactions passed:

Image title

The next interaction is to test search endpoints, literally the same URL with our get TODO list service, but with a query parameter q specified:

    - description: Search todo item list
      request:
        method: GET
        url: /todos
        params:
          q: A
      response:
        json:
          size: 1
          0:
            desc: Task A


This time, when we reload the browser, we get an oops:

Image title

Going back to the app console, we should find the following error message:

[FAIL] Search todo item list
error running scenario: Main
org.osgl.exception.UnexpectedException: Cannot verify value[0] with test [1]
at org.osgl.util.E.unexpected(E.java:179)
at act.e2e.Scenario.verifyValue(Scenario.java:569)
at act.e2e.Scenario.verifyList(Scenario.java:509)
at act.e2e.Scenario.verifyBody(Scenario.java:440)
at act.e2e.Scenario.verify(Scenario.java:406)


Looks like it returns an empty JSON array. Hmm... what causes this discrepancy? The code is actually missing a % before the letter A for query parameter q, as SQL expects % to be used with text when doing the like query, so let's add that and try again:

Image title

This is an even more shocking result, but actually not that scary. I didn't realize the % is a reserved character in YAML files, so let's wrap the %A with quotes, so that your interaction now should look like this:

    - description: Search todo item list
      request:
        method: GET
        url: /todos
        params:
          q: '%A'
      response:
        json:
          size: 1
          0:
            desc: Task A


Now refresh the browser — it gives us the green pass:

Image title

The last two interactions need to be added together. They are for deleting TODO items and verifying the test's effect:

    - description: Delete todo item by ID
      request:
        method: DELETE
        url: /todos/${last:0.id}
    - description: Verify delete effect
      request:
        method: GET
        url: /todos
      response:
        json:
          size: 0


Cool! No drama, it just passed:

Image title

So now let's say our TODO application is all done, with services implemented plus a set of fully automated end-to-end test cases! Let's take a look at code statistics:

luog@luog-X510UQR:/tmp/2/todo-service$ loc src
--------------------------------------------------------------------------------
 Language             Files        Lines        Blank      Comment         Code
--------------------------------------------------------------------------------
 XML                      1          115           20            7           88
 Java                     3           79           16            7           56
 YAML                     1           53            0            0           53
--------------------------------------------------------------------------------
 Total                    5          247           36           14          197
--------------------------------------------------------------------------------


Well with just 56 lines of Java source code plus 53 lines of YAML code, we get a workable and testable Java RESTful service with 5 endpoints! (The 88 lines of XML is the logback configuration that comes with the Maven archetype). Who said Java can't be an agile language?!

To wrap up our story, we need to show how act-e2e helps our CI service. as we don't expect CI to run in a browser in order to test our scenarios, right? Here is how to get things done when it really works in your CI: Just use the Maven command mvn -q clean compile act:e2e. It launches your application followed up with the e2e tests on whatever is defined in your scenario files:

luog@luog-X510UQR:/tmp/2/todo-service$ mvn -q clean compile act:e2e
Listening for transport dt_socket at address: 5005
 ___   _    _    _        __   _   _         ___   _   _ 
  |   / \  | \  / \  __  (_   |_  |_)  \  /   |   /   |_ 
  |   \_/  |_/  \_/      __)  |_  | \   \/   _|_  \_  |_ 

                  powered by ActFramework r1.8.8-RC8-7ed4

 version: v1.0-SNAPSHOT-db6c
scan pkg: 
base dir: /tmp/2/todo-service
     pid: 31412
 profile: e2e
    mode: DEV

     zen: Flat is better than nested.

2018-05-31 22:00:08,558 INFO  a.Act@[main] - loading application(s) ...
2018-05-31 22:00:08,579 INFO  a.a.App@[main] - App starting ....
2018-05-31 22:00:08,839 WARN  a.h.b.ResourceGetter@[main] - URL base not exists: META-INF/resources/webjars
2018-05-31 22:00:08,863 WARN  a.a.DbServiceManager@[main] - DB configuration not found. Will try to init default service with the sole db plugin: act.db.eclipselink.EclipseLinkPlugin@37ddb835
2018-05-31 22:00:10,529 WARN  a.m.MailerConfig@[main] - smtp host configuration not found, will use mock smtp to send email
2018-05-31 22:00:11,041 WARN  a.Act@[jobs-thread-2] - No data source user configuration specified. Will use the default 'sa' user
2018-05-31 22:00:11,041 WARN  a.Act@[jobs-thread-2] - No database URL configuration specified. Will use the default h2 inmemory test database
2018-05-31 22:00:11,041 WARN  a.Act@[jobs-thread-2] - JDBC driver not configured, system automatically set to: org.h2.Driver
2018-05-31 22:00:11,205 INFO  o.xnio@[main] - XNIO version 3.3.8.Final
2018-05-31 22:00:11,232 INFO  o.x.nio@[main] - XNIO NIO Implementation Version 3.3.8.Final
2018-05-31 22:00:11,392 INFO  a.Act@[main] - network client hooked on port: 5460
2018-05-31 22:00:11,394 INFO  a.Act@[main] - CLI server started on port: 5461
2018-05-31 22:00:11,396 INFO  a.Act@[main] - app is ready at: http://192.168.1.2:5460
2018-05-31 22:00:11,397 INFO  a.Act@[main] - it takes 4680ms to start the app

2018-05-31 22:00:11,449 INFO  a.a.App@[jobs-thread-2] - App[todo-service] loaded in 2870ms
2018-05-31 22:00:11,455 INFO  a.a.ApiManager@[jobs-thread-5] - start compiling API book
2018-05-31 22:00:11,606 INFO  a.a.ApiManager@[jobs-thread-5] - API book compiled
Start running E2E test scenarios

================================================================================
MAIN

Test TODO service
--------------------------------------------------------------------------------
[EL Info]: 2018-05-31 22:00:12.769--ServerSession(353000845)--EclipseLink, version: Eclipse Persistence Services - 2.7.1.v20171221-bd47e8f
[EL Info]: connection: 2018-05-31 22:00:12.94--ServerSession(353000845)--/file:/tmp/2/todo-service/./_default login successful
[PASS] Add one todo item
[PASS] Fetch todo item added in last interaction
[PASS] Fetch todo item list
[PASS] Search todo item list
[PASS] Delete todo item by ID
[PASS] Verify delete effect
--------------------------------------------------------------------------------
It takes 0s to run this scenario.

2018-05-31 22:00:13,391 INFO  a.a.App@[Thread-6] - App shutting down ....


Summary

In this post, we went through the whole process of creating a workable and testable RESTful TODO service. It actually takes no more than 20 minutes to get it worked out in less than 60 lines of Java code. This is the core value of ActFramework — focus on expressiveness and makes web developer's life easier.

Developers! Quickly and easily gain access to the tools and information you need! Explore, test and combine our data quality APIs at Melissa Developer Portal – home to tools that save time and boost revenue. Our APIs verify, standardize, and correct the Big 4 + more – name, email, phone and global addresses – to ensure accurate delivery, prevent blacklisting and identify risks in real-time.

Topics:
java ,test automation ,restful api testing ,end to end testing ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}