REST API or SOAP Testing Automation With ZeroCode JSON-Based Open Source Test Framework - CodeProject
REST API or SOAP Testing Automation With ZeroCode JSON-Based Open Source Test Framework - CodeProject
Declarative, Configurable and Super Easy API testing lib using YAML/JSON steps
It helps in clearly dividing your scenarios into tiny YAML/JSON steps which helps in identifying what exactly failed or passed during
an application end-to-end integration testing and consumer contract testing.
Introduction
Zerocode brings the simplicity in testing and validating APIs by eliminating repetitive code for test assertions, http calls and payload
parsing. See an example how. It's powerful response/payload comparison and assertions make the testing cycle a lot easy and clean.
AC1:
GIVEN- the POST api end point '/api/v1/users' to create an user,
WHEN- I invoke the API,
THEN- I will receive the 201 status with the a user ID and headers
AND- I will validate the response
AC2:
GIVEN- the REST api GET end point '/api/v1/users/${created-User-Id}',
WHEN- I invoke the API,
THEN- I will receive the 200(Ok) status with body(user details) and headers
AND- I will assert the response
will translate to the below executable JSON in Zerocode - Simple and clean !
You do not need to write POJOs or builders to represent or create your test inputs/results
You just need the YAML or JSON equivalent for your payload
All assertion mismatches for a response are displayed at once (see picture below)
It makes the cycle easy, either to correct the assertions or fix the application code
Response validation is seamless however hierarchal they may be. You can verify the entire response as it is -or- a part of it -
or- a specific field by using Jayway JSON path
The framework handles it for you. See examples here (Observe the verify section in the steps).
The framework enables you to override the behaviour by using your own custom client (optional)
Just pass the env value in -D param of maven. See example here.
JUnit test result reports enable fuzzy text matching with search and filter
Filter by author or filter by a scenario name -or- filter by PASS/FAIL status, etc. See examples here.
Suite runner is seamless, you do not have to copy/paste series of test-class names
Just point to the root of the test package. See an example here.
Optionally, copy/paste series of class names can be used as Junit Suite runners
Zerocode readme file has examples of all the above scenarios and much more usages with examples:
When assertion fails, i.e., the actual response doesn't match with "verify" block, it displays all non-matching fields by their JSON
path (enlarge the pic for clarity) like below:
Background
In today's world of automation, API testing sounds easy enough, but are we doing it in a way where these (tests) are easy to share,
easy to maintain and easy to change? Probably not, because too much time gets wasted in preparing the test code in order to
bring the final request fired against the target system, then too much code goes into assert, assertThat, assertNull,
assertTrue, etc. and the cycle goes on, if the objects are hierarchal in nature, the complexity simply increases in
serializing/deserializing/writing builders/pojo, etc. Then even if we take that approach, are we in a state where we could easily share
the pojos/builders/serializers/desers, etc. and explain to the interfacing team what's going on inside them? Does the debugging
become easy when the tests fail?
The consumer-contract tests should be readable and maintainable to both parties (service provider and service consumer), they
should look simple enough and must contain the request, response in a readable structure to Business, as we share the JSON
contracts with interfacing teams. Same goes with End to End integration tests too.
Zerocode goes extra miles and makes these aspects of testing life cycle so simple that you will do BDD and/or TDD without much
effort, but with full clarity and focusing on business scenarios and ACs (Acceptance Criterias) with much more efficiency and
accuracy.
//
// See the "verify" section below, if the response matches this JSON block, then the test will
PASS
//
{
"scenarioName": "Testing the GitHub REST end point",
"steps": [
{
"name": "get_user_details",
"url": "/users/octocat",
"method": "GET",
"request": {
"queryParams":{
//key-value pair
}
},
"retry": {
"max": 3,
"delay": 1000,
},
"verify": {
"status": 200,
"body": {
"login" : "octocat",
"id" : 583231,
"name" : "The Octocat",
"type" : "User"
}
}
}
]
}
import org.jsmart.zerocode.core.domain.JsonTestCase;
import org.jsmart.zerocode.core.domain.TargetEnv;
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
@TargetEnv("github_host.properties")
@RunWith(ZeroCodeUnitRunner.class)
public class JustHelloWorldTest {
@Test
@JsonTestCase("helloworld/hello_world_status_ok_assertions.yml")
public void testGetApi() throws Exception {
}
}
@RunWith(ZeroCodeUnitRunner.class)
@JsonTestCase("helloworld/hello_world_status_ok_assertions.yml")
@TargetEnv("github_host.properties")
Let's discuss them below.
@RunWith(ZeroCodeUnitRunner.class)
This unit runner extends and builds on the JUnit core runner, i.e., BlockJUnit4ClassRunner. The framework source code
goes as follows. See the code snippet below:
After it picks the test case, it decomposes the test scenario into one or more steps, each step having:
url - The FQDN (Fully Qualified Domain Name) of the REST application or a SOAP server, e.g., https://api.github.com
method - An Http method, e.g., GET/PUT/POST/DELETE, etc. all supported methods by Apache Http Client.
request - JSON request to the target system/url under test.
retry - Max retry with certain delay between the retries
queryParams - Key-Value pairs to filter the REST api output
verify - Expected response from server, i.e. the system under test.
Optionally, if a Java class is used in the URL, then the below breakdown comes into play, where:
Note: The "request", "verify" field above supports XML input too which helps in testing SOAP end points. Along with
optionally converting an XML to JSON equivalent (see MIME converters in the README) for easily readable and more granular
assertions field by field.
...
...
@Override
public void run(RunNotifier notifier) {
notifier.addListener(new ZeroCodeTestReportListener(...);
super.run(notifier);
}
...
...
The "run()" method of the "BlockJUnit4ClassRunner" has been overridden here to aggregate the test result, once all
tests have been completed running.
Once the test run starts (via JUnit runner), the payLoad is picked from the "request" field, and gets invoked against the FQDN
with full path.
...
executionResult = serviceExecutor.executeRESTService
(serviceName, operationName, resolvedRequestJson)
...
final javax.ws.rs.core.Response serverResponse =
httpClient.execute(httpUrl, methodName, headers, queryParams, bodyContent);
@JsonTestCase("helloworld/hello_world_status_ok_assertions.yml")
This annotation tells the "ZeroCodeUnitRunner" to pick the test case from a specified folder in the resources.
...
JsonTestCase annotation = method.getMethod().getAnnotation(JsonTestCase.class);
if (annotation != null) {
currentTestCase = annotation.value();
} else {
currentTestCase = method.getName();
}
...
@TargetEnv("github_host.properties")
This annotation holds the host/port/context properties of the target application under test.
restful.application.endpoint.host=https://api.github.com
restful.application.endpoint.port=443
restful.application.endpoint.context=
Inside the framework, these properties get appended to the relative path mentioned in the "url" and then get invoked with the
payLoad. In the above example, it is resolved to https://api.github.com:443/users/octocat.
The properties are bound and injected via "Google Guice" as below code inside the framework:
@Override
public void configure() {
/*
* Install other guice modules
*/
...
install(new HttpClientModule());
...
/*
* Bind properties for localhost, CI, SIT, PRE-PROD etc
*/
Names.bindProperties(binder(), getProperties(serverEnv));
}
In case of a Java method execution, the framework executes the method via reflection and returns an equivalent JSON as the
response. (See the framework source code for >>
org.jsmart.zerocode.core.engine.executor.JsonServiceExecutor, the method:
executeJavaService()).
...
//guice
@Inject
private JavaExecutor javaExecutor;
try {
Object request = objectMapper.readValue(requestJson, argumentTypes.get(0));
Object result = javaExecutor.execute(serviceName, methodName, request);
return prettyPrintJson(resultJson);
} catch (Exception e) {
e.printStackTrace();
}
}
...
Points of Interest
In the "steps" section above, you can add multiple steps to make up a scenario using ${JSON Path to earlier steps}
e.g., See below:
//
// See the "verify" section below, if the response matches this section, then the test will
PASS
//
{
"scenarioName": "GIVEN- the REST end points, WHEN- I invoke POST and GET,
THEN- I will create and receive the new emp details",
"steps": [
{
"name": "create_emp",
"url": "/api/v1/google-uk/employees",
"method": "POST",
"request": {
"body": {
"id": 1000,
"name": "Larry Pg",
"addresses": [
{
"gpsLocation": "x9000-y9000z-9000-home"
},
{
"gpsLocation": "x9000-y9000z-9000-home-off"
}
]
}
},
"verify": {
"status": 201,
"body": {
"id": 1000
}
// You can have more assertions here, but for now I am interested in
// whether the POST has created the "id" or not.
// If the assertion fails here, then it will error out until
// you fix the target application.
}
},
{
"name": "get_user_details",
"url": "/api/v1/google-uk/employees/${$.create_emp.response.body.id}", //reuse
value
"method": "GET",
"request": {
},
"verify": {
"status": 200,
"body": {
"id": 1000,
"name": "Larry Pg",
"addresses": [
{
"gpsLocation":
"${$.create_emp.response.body.addresses[0].gpsLocation}"
},
{
"gpsLocation": "x9000-y9000z-9000-home-off"
}
]
}
}
}
]
}
import javax.ws.rs.core.Response;
Inside the "execute(...)" method, you simply use your own "custom HttpClient" to invoke the REST/SOAP endpoint,
then return the "Response" object after mapping your actual response.
History
You can find release history and more about the framework in the GitHub Zerocode README file.
Current release is 1.3.x, please visit zerocode in maven central for latest releases.
Over several brain storming sessions, automation testers and developers came to a common conclusion to write tests:
That will allow anyone to change, share and maintain them easily
Avoid writing boiler plate sticky code.
Large numbers of tests to be well organized and business-readable
Local Laptop to be configured for multiple environments (localhost, ci/dev/dit, SIT, UAT, PRE-PROD, etc.) to fire the tests
easily like JUnit tests.
Outcome of the sessions was to consider a steps based scenario build-up approach where the underlying test and test data is
picked from a referenced JSON file.
YAML/JSON is widely supported by most popular IDEs and hence proved to be very efficient approach.
Looking Towards Contributing to this Open Source Library?
It's easy! Please start with raising an issue and follow the guidelines in CONTRIBUTING.md.
License
This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0