NAV Navbar
java

JDI Dark Framework

JDI Dark is a simple test automation framework for web service testing powered by REST Assured.

Highlights

Simple JDI Dark examples

1. Service Object Model

@ServiceDomain("http://httpbin.org/")
public class ServiceExample { 
    @GET("/get") RestMethod getInfo;
    @POST("/post") RestMethod post;
    @PUT("/put") RestMethod put;
    @PATCH("/patch") RestMethod patch;
    @DELETE("/delete") RestMethod delete;
}

JDI Dark provides capability to describe your API as a simple Service Object. That allows you to unify and combine your REST methods and endpoints in one class (similar to UI Page Object pattern).

Let's say we have a website for REST testing located at the following address: http://httpbin.org and we need to test some of its endpoints. We can create a Java class and describe the methods we want to test.

Isn't that simple?

2. Create simple test using Service Object

@Test
public void simpleRestTest() {
    RestResponse resp = ServiceExample.getInfo.call();
    resp.isOk().
      body("url", equalTo("http://httpbin.org/get")).
      body("headers.Host", equalTo("httpbin.org")).
      body("headers.Id", equalTo("Test"));
    resp.assertThat().header("Connection", "keep-alive");
}

Based on the Service Object class we created before, we can create a simple test to execute the HTTP GET method and validate the response body.

  1. Make a request call to the HTTP method described in your Service Object class.
  2. Validate the response body.

The HTTP GET method described in the service class is being called here. Next, the response is being validated.

You can check other tests by following the link:

JDI Dark test examples

3. Create tests without using Service Object

@Test
public void noServiceObjectTest(){
    RestResponse resp = GET(requestData(
      rd -> {rd.url="http://httpbin.org/get";
           rd.headers=new MapArray<>(new Object[][]{
              {"Name","Roman"},
              {"Id","TestTest"}
           });}
      ));
}

As it has been mentioned, you are still able to write tests without using Service Object model. It is possible to call HTTP methods directly from your tests. All you need is just to provide required information within method arguments.

In this test we are making an HTTP GET request with invoked request data containing URL and headers.

4. Query Parameters, Headers, Cookies support

@QueryParameter(name = "test", value = "test")
@GET("/get") RestMethod getInfo;

@QueryParameter(name = "param1", value = "test")
@QueryParameter(name = "param2", value = "test")
@GET("/get") RestMethod getInfo;

@Header(name = "Name", value = "Roman")
@Header(name = "Id", value = "Test")
@Cookie(name = "session_id", value = "1234")
@GET("/get") RestMethod getInfo;

JDI Dark supports query parameters, so you can specify them in your service class. You just need to use the @QueryParameter annotation in your method or service class description.

If you need to specify several query parameters, do it with the @QueryParameters annotation.

The same way you can specify headers and cookies. JDI Dark has a special annotation for them as well.

5. Performance testing support

    @Test
    public void concurrentTest() throws InterruptedException, ExecutionException {
        PerformanceResult pr = RestLoad.loadService(5, 10, ServiceExample.getInfo);
        Assertions.assertThat(pr.getNumberOfFails()).describedAs("Fails found").isEqualTo(0);
        Assertions.assertThat(pr.getAverageResponseTime()).describedAs("The average response time is greater than expected.").isLessThan(1000);
        Assertions.assertThat(pr.getMaxResponseTime()).describedAs("The maximum response time is greater than expected.").isLessThan(3000);
    }

JDI Dark supports simple performance testing. There is com.epam.http.performance package available that contains several classes and methods for collecting response statistics.

You can load your service and get the response time and the number of fails compared to the amount of requests. Just use the loadService() method with the parameters you need.

Tutorial

In this tutorial we’ll take a glance at JDI Dark, a library that simplifies test automation, makes test run results stable, predictable and easy to maintain.

  1. Quick Start - a short instruction on how to add JDI Dark to your project and perform its basic configuration.
  2. JDI Dark at a glance - a simple example of creating test cases for REST services with JDI Dark.
  3. JDI Dark Service Objects - a Service Object class description
  4. JDI Dark and Cucumber - a short instruction on how to create cucumber feature files with JDI Dark.
  5. JDI Dark and SOAP - a short instruction on how to create test cases for SOAP with JDI Dark.

1. Quick Start

Maven Dependencies

<dependency>
    <groupId>com.epam.jdi</groupId>
    <artifactId>jdi-dark</artifactId>
    <version>{RELEASE}</version>
</dependency>

First, you need to add JDI Dark to the dependency section of your pom.xml file. The latest version can be found in the Maven Central Repository.

Configuration

Create test.properties file on path src/test/resources to configure the project.

Add domain and log.level into the properties file.

domain=local=http://localhost:8080, trello=https://api.trello.com/1
log.level=WARNING

There are tree ways to specify your domain:

- If there is only one domain, specify domain=URI in the properties file. Then you can use the service domain annotation this way: @ServiceDomain("${domain}")

- In case of a list of domains, specify domain=nameDomain=URI, nameDomain2=URI. Then use these variables in the service domain annotation:
@ServiceDomain("${nameDomain}")

@ServiceDomain("${nameDomain2}")

- It's possible to specify the domain directly in the service domain annotation @ServiceDomain("https://api.trello.com/1")

Available options: INFO, WARNING, OFF, FATAL, ERROR, STEP, DEBUG, TRACE, ALL

See example on GitHub.

2. JDI Dark at a glance

//STEP 1 Creating class describing Board object with needed fields   
public class Board {
    private String id;
    private String name;

    public String getId() {return id;}
    public void setId(String id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}


//STEP 2 Creating class Service Object Model
@ServiceDomain("${trello}")
@QueryParameter(name = "key", value = "3445103a21ddca2619eaceb0e833d0db")
@QueryParameter(name = "token", value = "a9b951262e529821308e7ecbc3e4b7cfb14a24fef5ea500a68c69d374009fcc0")
public class TrelloService {

    public static final String BOARDS = "/boards";

    @ContentType(JSON)
    @POST(BOARDS)
    public static RestMethod<Board> boardsPost;

    public static Board createBoard(Board board) {
        return boardsPost.post(board, Board.class);
    }
}

//STEP 3 Creating class for generating needed data
public class TrelloDataGenerator {

    public static Board generateBoard() {
        Board board = new Board();
        board.setName("JDI Test Board number " + new Random().nextInt(Integer.MAX_VALUE));
        return board;
    }
}


//STEP 4 Creating  test class
public class TrelloTests {

    @BeforeClass
    public void initService() {
        init(TrelloService.class);
    }

    @Test
    public void createCardInBoard() {    
        Board board = TrelloDataGenerator.generateBoard();
        Board createdBoard = TrelloService.createBoard(board);
        Board gotBoard = TrelloService.getBoard(createdBoard.getId());
        Assert.assertEquals(gotBoard.getName(), createdBoard.getName(), "Name of created board is incorrect");
    }
}

First test creation

In this example we create test for Trello /boards/ endpoint. You can familiarize with Trello API here.


1. Create class describing REST object

Create class for board object with needed fields. Create get and set methods for these fields.

See example class for Card Object.


2. Create service class with Rest methods

See example class for TrelloService .


3. Create class helper for generating data if needed

In our example we created TrelloDataGenerator class which contains method for generating Trello board

See example class for generating Trello data.


4. Create test class
- Initialize your service class in @BeforeClass

- Add test methods

See test case example.

Running test examples

You can find a lot of test examples JDI DARK project . Before first running, execute maven commands:

mvn clean
mvn compile

3. JDI Dark Service Objects

JDI Dark provides Service Object class to describe web services. Service Object class allows to create well-structured code.
See the examples here

4. JDI Dark and Cucumber

//STEP 1 Service Object Model class creation
@ServiceDomain("http://httpbin.org/")
public class ServiceExample {
    @ContentType(JSON)
    @GET("/get")
    @Header(name = "Name", value = "Roman")
    @Header(name = "Id", value = "Test")
    RestMethod getMethod;

    @ContentType(JSON)
    @GET("/get")
    RestMethod get;

    @Header(name = "Type", value = "Test")
    @POST("/post")
    RestMethod postMethod;

    @PUT("/put")
    RestMethod putMethod;
    @PATCH("/patch")
    RestMethod patch;
    @DELETE("/delete")
    RestMethod delete;
    @GET("/status/{status}")
    RestMethod status;
}
#STEP 2, 4 Feature file creation (pre-created steps from JDI Dark BDD are used here)
Feature: Json response check

  Scenario: Check json response
    Given init service example
    And set request content type to 'JSON'
    When perform 'getMethod' request
    Then response status type is OK
    And response body has values
      | url          | http://httpbin.org/get |
      | headers.Host | httpbin.org            |
    And response header "Connection" is "keep-alive"
//STEP 3 TestRunner creation (TestNG example)
@CucumberOptions(features = "src/test/resources/features/",
        glue = {"com/epam/jdi/httptests/steps", "com/epam/jdi/http/stepdefs/en"},
        tags = {"@smoke"})
public class HttpTestsRunner extends AbstractTestNGCucumberTests {
}

In this example we will create tests for simple HTTP Request & Response Service. You can get acquainted with API here.


1. Create service class with Rest methods

-Specify BaseURI service by using @ServiceDomain("http://httpbin.org/") annotation before class

-Describe endpoints using annotations:

See example of the Service Object class.

2. Create feature files

See examples of the feature files.

3. Create TestRunner class (JDI Dark supports both TestNG and Junit)

Specify additional options/custom properties via @CucumberOptions (‘strict’, tags’, ‘plugin’, ‘features’, ‘glue’, ‘format’). For more details on @CucumberOptions and configs check cucumber documentation here.

Junit TestRunner example.

TestNG TestRunner example.

4. Create step definitions in Java

Check all pre-created JDI Dark BDD steps here.

Check the example here.

5. Install Cucumber plugin for IDEA

Go File->Settings-->Plugins. Then add Cucumber plugin. This plugin redirects you to the step definition when clicking the step.

6. Create scenario description in Gherkin in feature file.

You can see the examples of feature files here.

7. Run the tests via any available option

5. JDI Dark and SOAP

In this example we will create a tests for SOAP service yandex-speller with WSDL https://speller.yandex.net/services/spellservice?WSDL

1. Generate objects from WSDL

Generate objects classes from WSDL using SOAP objects generation from WSDL

2. Create the Service object class

@ServiceDomain("http://speller.yandex.net/services/spellservice")
public class YandexSpeller {

    @POST()
    public static SoapMethod<CheckTextRequest, CheckTextResponse> checkText;

    @POST()
    public static SoapMethod<CheckTextsRequest, CheckTextsResponse> checkTexts;
}
  1. Specify BaseURI service by using @ServiceDomain("https://speller.yandex.net/services/spellservice") annotation before class

  2. Describe SOAP methods using SoapMethod class. For example, for checking text - public static SoapMethod<CheckTextRequest, CheckTextResponse>

  3. Add annotation @POST() for your method

You can use other necessary annotations as described here.

See an example for Service Object class.

3. Create the test class

public class YandexSpellerSOAPTests {
    @BeforeTest
    public void before() {
        init(YandexSpeller.class);
    }

    @Test
    public void checkTestResponse() {
        CheckTextResponse response = YandexSpeller.checkText.callSoap(new CheckTextRequest().withText("soap").withLang("en"));
        Assertions.assertThat(response.getSpellResult().getError().size()).isZero();
    }
}

1.Initialize your service class in @BeforeClass

2.Add test methods

See a test case example.

API Testing Framework structure

JDI Dark includes the packages:

Documentation

Framework usage

JDI Dark Framework provides great opportunities for web services testing.
Below you will find the description of JDI's features and the ways of using them with code examples.

HTTP methods

JDI Dark supports the following HTTP methods:

For all these methods there are annotations named after them in the com.epam.http.annotations.* package. All of these annotations take the call request URI value. The annotations are supposed to be used in your Service Object class.

To point to the base URI of your service, it's convenient to use the @ServiceDomain annotation with URL provided as argument. Then, values in your method annotations might be just specific URL paths.

Specifying HTTP method

@POST("/greet")
public static RestMethod greetPost;

@DELETE("/body")
public static RestMethod deleteBody;

@GET("/hello")
public static RestMethod getHello;

@PUT("/cookie")
public static RestMethod putCookie;

@Test
public void formParamsAcceptsIntArgumentsJDI() {
        RestResponse response = greetPost
                .call(formParams()
                .addAll(new Object[][]{{"firstName", 1234}, {"lastName", 5678}}));
        response.isOk().body("greeting", equalTo("Greetings 1234 5678"));
    }

@Test
public void deleteSupportsStringBody() {
    RestResponse response = deleteBody.call(requestBody(TEST_BODY_VALUE));
    response.assertThat().body(is(TEST_BODY_VALUE));
}

@Test
public void getCanReturnResponseDataAsString() {
    RestResponse response = JettyService.getHello.call();
    final String responseInfo = response.toString();
    assertThat(responseInfo, containsString("Response status: 200 OK (OK)"));
    assertThat(responseInfo, containsString("Response body: {\"hello\":\"Hello Scalatra\"}"));
}

@Test
public void putCanReturnBodyAsString() {
    Map<String, Object> cookies = new HashMap<>();
    cookies.put("username", "John");
    cookies.put("token", "1234");
    final String body = JettyService.putCookie.call(cookies().addAll(cookies)).getBody();
    assertEquals("username, token", body);
}

For describing HTTP method use RestMethod class with appropriate annotation in Service object class.
@POST("endpoint") - for POST method
@GET("endpoint") - for GET method
@DELETE("endpoint") - for DELETE method
@PUT("endpoint") - for PUT method

Test example in Java

Available methods for sending HTTP requests in JDI Dark:

Method Description Return Type
call() send request RestResponse
call(JAction1 action) send request with Request Data parameters RestResponse
call(RequestData requestData) send request with Request Data parameters RestResponse
call(RequestSpecification requestSpecification) send request with RequestSpecification RestResponse
callBasedOnSpec(RequestSpecification requestSpecification) send request with RequestSpecification RestResponse
call(RestAssuredConfig restAssuredConfig) send request with RestAssuredConfig RestResponse
callAsData(Class c) send request and map response to Java object java object
callAsData() send request java object
queryParams(String queryParams) send request with query parameters RestMethod
pathParams(Object... pathParams) send request with named query parameters RestResponse
body(Object body) send HTTP request with body RestMethod
data(RequestData requestData) Send HTTP request with invoked request data RestMethod
post(Object body) send post/put request with body RestResponse
post(Object body, Class c) send post/put request with body and parse result to object java object
postAsData(Object object) send post/put request with body java object


Test examples in Java

Request Data

You might need to use specific request data in your requests. Cookies, headers, query parameters and Content-type are available in annotated form. Therefore, you can specify them in your Service Object class providing name and value.

There is a class called com.epam.http.requests.RequestData which represents the data sent along with your request.

It is also possible to specify request data when making a request call.

Request body

public static RequestData body(Object body)
public static RequestData requestData(JAction1<RequestData> valueFunc)

Request body can be set when making a call request. Just pass your request body as an argument to the call() method or within the RequestData object with the following fields:

All of these fields can be set/updated from the call() method as well.

You may need to statically import the com.epam.http.requests.RequestData.* package.

public RestResponse call(RequestSpecification requestSpecification) 

Another way to generate request data is using Request Data object as a parameter
JDI Dark also supports making request calls with Rest Assured request specification

Path parameters

@GET("/{firstName}/{lastName}")
public static RestMethod getUser;

@Test
public void supportsPassingPathParamsToRequestSpec() {
    Object[][] pathParams = new Object[][]{{"firstName", "John"}, {"lastName", "Doe"}};
    RestResponse response = getUser.call(pathParams().addAll(pathParams));
    response.isOk().body("fullName", equalTo("John Doe"));
}

@Test
public void canSpecifySpacePathParamsWithoutKey(){
  RestResponse response = getUser.pathParams("John", " ").call();
  response.isOk().body("firstName", equalTo("John")).body("lastName", equalTo(" "));
}

@Test
public void urlEncodesPathParamsInMap(){
  final Map<String, String> params = new HashMap<>();
  params.put("firstName", "John: å");
  params.put("lastName", "Doe");
  RestResponse response = getUser
          .call(pathParams().addAll(params));
  response.isOk().body("fullName", equalTo("John: å Doe"));
}

@GET("/status/{status}?q={value}") RestMethod statusWithQuery;

@Test
public void statusTestWithQueryInPath() {
    RestResponse resp = service.statusWithQuery.pathParams("503", "some").call();
    assertEquals(resp.status.code, 503);
    assertEquals(resp.status.type, SERVER_ERROR);
    resp.isEmpty();
}

A URL can contain one or several path parameters, each denoted with curly braces, e.g. /get/{board_id}, /boards/{board_id}/cards/{short_card_id}/. You can use them in your Service Object methods and replace placeholders with values in request calls.

Methods for sending path parameters to RequestData:

Method Description Return Type
pathParams().add(paramName, paramValue) pass one parameter to a path RequestData
pathParams().addAll(Object[][] array2D) pass multiple parameters to a path RequestData
pathParams().addAll(Map map) pass multiple parameters to a path RequestData

Class SpecUpdater contains all variants of path parameters supported by pathParams().

Methods for passing path parameters (with/without query params) in RestMethod:

Method Description Return Type
pathParams(Object... pathParams) pass parameters to a path RestMethod


Test examples in Java

Query parameters

@GET("/greet")
public static RestMethod getGreet;

@Test
public void whenLastParamInGetRequestEndsWithEqualItsTreatedAsANoValueParam() {
  Map<String, String> queryParamsMap = new HashMap<>();
  queryParamsMap.put(FIRST_NAME, FIRST_NAME_VALUE);
  queryParamsMap.put(LAST_NAME, StringUtils.EMPTY);
  JettyService.getGreet.call(rd -> rd.queryParams.addAll(queryParamsMap))
                .isOk()
                .assertThat()
                .body("greeting", equalTo("Greetings John "));
}

@DELETE("/greet")
public static RestMethod deleteGreet;

@Test
public void bodyHamcrestMatcherWithOutKey() {
    deleteGreet.call(queryParams().addAll(
          new Object[][]{{FIRST_NAME, FIRST_NAME_VALUE},
                    {LAST_NAME, LAST_NAME_VALUE}}))
          .isOk().assertThat()
          .body(equalTo("{\"greeting\":\"Greetings John Doe\"}"));
}

@GET("/noValueParam")
public static RestMethod getNoValueParam;

@Test
public void singleNoValueQueryParamWhenUsingQueryParamInUrlForGetRequest() {
JettyService.getNoValueParam.queryParams("some").call()
            .isOk()
            .assertThat()
            .body(is("Params: some="));
}

@GET("/{channelName}/item-import/rss/import?source={url}")
public static RestMethod getMixedParam;

@Test
public void mixingUnnamedPathParametersAndQueryParametersWorks() {
  RestResponse response = getMixedParam.pathParams("games", "http://myurl.com").call();
  assertEquals(response.getBody(), "Not found");
}

Methods for sending query parameters to RequestData:

Method Description Return Type
queryParams().add(paramName, paramValue) pass one parameter to a path RequestData
queryParams().addAll(Object[][] array2D) pass multiple parameters to a path RequestData
queryParams().addAll(Map map) pass multiple parameters to a path RequestData

The method allows sending of specific query parameters in URL in RestMethod:

Method Description Return Type
queryParams(String queryParams) pass query parameters RestMethod

Class SpecUpdater contains all variants of parameters supported by queryParams().

If you need to add both path parameter and query parameter to your request, use pathParams(Object... pathParams).

Test examples in Java











Form parameters

@POST("/greet")
public static RestMethod greetPost;

@Test
public void formParamsAcceptsIntArgumentsJDI() {
RestResponse response = greetPost
            .call(formParams()
            .addAll(new Object[][]{{"firstName", 1234}, {"lastName", 5678}}));
response.isOk().body("greeting", equalTo("Greetings 1234 5678"));
}

Methods for sending form params to RequestData:

Method Description Return Type
formParams().add(paramName, paramValue) pass one form parameter to a path RequestData
formParams().addAll(Object[][] array2D) pass multiple parameters to a path RequestData
formParams().addAll(Map map) pass multiple parameters to a path RequestData

Class SpecUpdater contains all variants of parameters supported by formParams().

Multipart parameters

@POST("/multipart/file")
@MultiPart(controlName = "file", fileName = "myFile")
public static RestMethod postMultiPartFile;

@Test
public void multiPartByteArrays() throws Exception {
    final byte[] bytes = IOUtils.toByteArray(getClass()
.getResourceAsStream("/car-records.xsd"));
    JettyService.postMultiPartFile.multipart(bytes).call().assertThat()
                .statusCode(200).body(is(new String(bytes)));
    }

@POST("multipart/multiple")
public static RestMethod postMultipartMultiple;

@Test
public void multiPartUploadingWorksForFormParamsAndByteArray() {
    JettyService.postMultipartMultiple.call(requestData(rd -> {
        rd.formParams.add("formParam1", "");
        rd.formParams.add("formParam2", "formParamValue");
        rd.setMultiPart(new MultiPartSpecBuilder("juX").controlName("file"));
        rd.setMultiPart(new MultiPartSpecBuilder("body").controlName("string"));
    })).assertThat()
            .statusCode(200)
            .body(containsString("formParam1 -> WrappedArray()"));
}

Methods for sending multipart parameters to request data:

Method Description Return Type
setMultiPart(MultiPartSpecBuilder multiPartSpecBuilder) set multipart parameters RequestData

Use MultiPartSpecBuilder class for creating advanced multi-part requests.

Test examples in Java













Using Request Data object as a parameter

@POST("/greet")
public static RestMethod greetPost;

@Test
public void charsetIsReallyDefined() {
    Map<String, String> formParamsMap = new HashMap<>();
    formParamsMap.put("firstName", "Some & firstname");
    formParamsMap.put("lastName", "<lastname>");
    RestResponse resp = greetPost.call(rd -> {
          rd.setContentType("application/x-www-form-urlencoded; charset=ISO-8859-1");
          rd.formParams.addAll(formParamsMap);
    });
    resp.isOk()
         .assertThat()
         .body("greeting", equalTo("Greetings Some & firstname <lastname>"));
}

Method for sending requests with invoked request data in RestMethod:

Method Description Return Type
call(RequestData requestData) make request with parameters indicated by Request Data RestResponse


Test examples in Java





RequestSpecification configuration

@GET("/jsonStore")
public static RestMethod getJsonStore;

@Test
public void supportsConfiguringJsonConfigProperties() {
  RestResponse resp = getJsonStore.call(RestAssuredConfig.newConfig().
           jsonConfig(JsonConfig.jsonConfig().
                   numberReturnType(JsonPathConfig.NumberReturnType.BIG_DECIMAL)));
  resp.isOk()
           .rootPath("store.book")
           .body("price.min()", is(new BigDecimal("8.95")))
           .body("price.max()", is(new BigDecimal("22.99")));
}

You can set RequestSpecification for your request. Get RestAssured RequestSpecification from RestMethod object, set and use it.

Method Description Return Type
getInitSpec() get RestAssured RequestSpecification RequestSpecification
call(RequestSpecification requestSpecification) make request with RequestSpecification RestResponse


Test examples in Java

For general setting RestAssured config see Accessing RestAssured

Tests without Service Object

public static RestResponse GET(RequestData data)
public static RestResponse GET(String url)
public static RestResponse GET(String url, RequestSpecification requestSpecification) 

All the HTTP methods mentioned before are also available in non-annotated form. They can be made accessible by importing com.epam.http.requests.RestMethods.* with several signatures.

You can call these methods with either of given arguments:

Tests with Service Object

Create service

@ServiceDomain("https://httpbin.org/")
public class ServiceExample {
    @ContentType(JSON) @GET("/get")
    @Header(name = "Name", value = "Roman")
    @Header(name = "Id", value = "Test")
    static RestMethod<Info> getInfo;

    @Header(name = "Type", value = "Test")
    @POST("/post")
    RestMethod postMethod;

    @PUT("/put") RestMethod putMethod;
    @PATCH("/patch") RestMethod patch;
    @DELETE("/delete") RestMethod delete;
    @GET("/status/{status}") RestMethod status;

    @Cookie(name = "session_id", value = "1234")
    @Cookie(name = "hello", value = "world")
    @GET("/cookies")
    public RestMethod getCookies;
} 

@ServiceDomain("http://localhost:8080")
public class JettyService {
    @POST("/noValueParam")
    @FormParameter(name = "some1", value = "one")
    public static RestMethod postNoValueParamWithPreDefinedFormParam;

    @QueryParameter(name = "firstName", value = "John")
    @QueryParameter(name = "lastName", value = "Doe")
    @GET("/greetXML")
    public static RestMethod getGreetXml;

    @POST("/multipart/file")
    @MultiPart(controlName = "file", fileName = "myFile")
    public static RestMethod postMultiPartFile;

    @GET("/greetJSON")
    @Proxy(host = "127.0.0.1", port = 8888, scheme = "http")
    public static RestMethod getGreenJSONWithProxyParams;

    @URL("http://www.google.se")
    @GET("/search?q={query}&hl=en")
    public static RestMethod searchGoogle;
}

@RetryOnFailure
@ServiceDomain(value = "http://localhost:8080/")
public class RetryingService {
    @GET(value = "503")
    @RetryOnFailure(numberOfRetryAttempts = 2, delay = 1, unit = TimeUnit.SECONDS)
    public static RestMethod get503;

    @GET(value = "502")
    @IgnoreRetry
    public static RestMethod ignoreRetrying;
}

@ServiceDomain("https://localhost:8443")
public class JettyServiceHttps {
    @GET("/jsonStore")
    @TrustStore(pathToJks = "src/test/resources/truststore_mjvmobile.jks", 
password = "test4321")
    public static RestMethod getJsonStore;
}

@ServiceDomain("http://www.dneonline.com/calculator.asmx")
@SOAPNamespace("http://tempuri.org/")
public class DneOnlineCalculator {
    @POST()
    @SOAPAction("http://tempuri.org/Multiply")
    @SOAP12
    public static SoapMethod<Multiply, MultiplyResponse> multiply;
}

It's possible to describe tested web service as a Service Object class using annotations:

Create tests for service

public class ServiceTest {

    private ServiceExample service;

    @BeforeClass
    public void before() {
        service = init(ServiceExample.class);
    }

    @Test
    public void simpleRestTest() {
        RestResponse resp = ServiceExample.getInfo.call();
        resp.isOk().
                body("url", equalTo("https://httpbin.org/get")).
                body("headers.Host", equalTo("httpbin.org")).
                body("headers.Id", equalTo("Test"));
        resp.assertThat().header("Connection", "keep-alive");
    }

    @Test
    public void serviceInitTest() {
        RestResponse resp = service.postMethod.call();
        resp.isOk().assertThat().
                body("url", equalTo("https://httpbin.org/post")).
                body("headers.Host", equalTo("httpbin.org"));
    }
}

This class can be initialized in tests. Fields of initialized object can be used to send requests from tests.





















Use predefined RestSpecification

public class ServiceTest {

    private RequestSpecification requestSpecification;
    private ServiceExample service;

    @BeforeClass
    public void before() {
        requestSpecification = given().filter(new AllureRestAssured());
        requestSpecification.auth().basic("user", "password");
        service = init(ServiceExample.class, requestSpecification);
    }
}

It's possible to setup already used RestSpecification. Predefined settings will be used for all endpoints of that service. In this example basic auth credentials will be passed to all endpoints.






Working with objects

There are key ability for any Rest Client is working with objects:

Setup Object Mapper

JDI allows the user to setup Object Mapper for serialization/deserialization objects within endpoints. After Object Mapper is set it will be used in all requests implicitly.

public class BaseTest {

    //Set global object mapper for whole project
    @BeforeSuite
    public void preconditions() {
        RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory(
                new Jackson2ObjectMapperFactory() {
                    @Override
                    public ObjectMapper create(Type type, String s) {
                        ObjectMapper objectMapper = new ObjectMapper();
                        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                        return objectMapper;
                    }
                }
        ));
    }

    //Set object mapper for service. In this case global mapper will be overridden for SimpleService's endpoints
    @BeforeClass
    public void before() {
        ObjectMapper objectMapper = new Jackson2Mapper(new Jackson2ObjectMapperFactory() {
            @Override
            public com.fasterxml.jackson.databind.ObjectMapper create(Type type, String s) {
                com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                return objectMapper;
            }
        });
        init(SimpleService.class, objectMapper);
    }
}

There are 2 ways to setup Object Mapper:

If Object Mapper has not been set by default, RestAssured mapper will be used.




















Using objects in tests

 public class FlowTests {

    @Test
    public void assignBoardToOrganization() {

        //Create organization
        Organization organization = TrelloDataGenerator.generateOrganization();
        //Send object to POST request and get response body as object
        Organization createOrg = TrelloService.createOrganization(organization);

        //Create board
        Board board = TrelloDataGenerator.generateBoard();
        board.setIdOrganization(createOrg.getId());
        //Send object to POST request
        TrelloService.createBoard(board);

        //Check that organization contains created board
        //Send GET request and get List of objects from response
        List<Board> boards = TrelloService.getOrganizationBoards(createOrg);
        Assert.assertTrue(boards.stream().map(Board::getName).collect(Collectors.toList()).contains(board.getName()), "Board wasn't added to organization");
    }
  }

JDI allows creating tests on business language using Service classes and working with objects.





















 public class ServiceExample {

    @ContentType(JSON)
    @POST("/organizations")
    public static RestMethod<Organization> createOrganization;

    //wrapper method for working with objects
    //send Organization object as request body and return response as Organization object
    public static Organization createOrganization(Organization organization) {
        return createOrganization.post(organization, Organization.class);
    }

    @ContentType(JSON)
    @GET("/organizations/{id}/boards")
    public static RestMethod<Board[]> getOrganizationBoards;

    //send GET request and return response as List of objects
    public static List<Board> getOrganizationBoards(Organization organization) {
        return Arrays.asList(getOrganizationBoards.call(requestPathParams("id", organization.getId())).getRaResponse().as(Board[].class));
    }
}

For convenient working with objects user can add additional wrapper methods to service classes.














Error handling

public interface ErrorHandler {
    boolean hasError(RestResponse restResponse) throws IOException;
    void handleError(RestResponse restResponse) throws IOException;
}

//example of implementation and using ErrorHandler
public class ErrorHandlerTests {
    private ServiceSettings serviceSettings;

    @BeforeClass
    public void initServiceSettings() {
        ErrorHandler errorHandler = new ErrorHandler() {
            @Override
            public boolean hasError(RestResponse restResponse) throws IOException {
                //only Client errors will be caught
                return ResponseStatusType.getStatusTypeFromCode(restResponse.getStatus().code) == ERROR;
            }

            @Override
            public void handleError(RestResponse restResponse) throws IOException {
                Assert.fail("Exception is caught: " + restResponse.toString());
            }
        };
        serviceSettings = ServiceSettings.builder().errorHandler(errorHandler).build();
    }

    @BeforeClass(dependsOnMethods = {"initServiceSettings"})
    public void initService() {
        init(TrelloService.class, serviceSettings);
    }
}

JDI Dark provides customized catching and processing of any unexpected responses.

To do this just inject the implementation of ErrorHandler interface into RestMethods through call Service Object init() method.

By default JDI Dark uses DefaultErrorHandler class for server (5XX) and client(4XX) errors.

Logging

private static JFunc2<RestMethod, List<RequestData>, String> LOG_REQUEST_TEMP;
private static JAction2<RestResponse, String> LOG_RESPONSE_TEMP;

@BeforeClass
public void initService() {
    init(JettyService.class);
    LOG_REQUEST_TEMP = LOG_REQUEST;
    LOG_RESPONSE_TEMP = LOG_RESPONSE;
    LOG_REQUEST = this::logRequest;
    LOG_RESPONSE = this::logResponse;
}

private String logRequest(RestMethod restMethod, List<RequestData> requestData) {
    MultiMap<String, String> queryparams = new MultiMap<>();
    for (RequestData rd : requestData) {
        queryparams.addAll(rd.queryParams);
    }
    String message = String.format("Do %s %s", restMethod.getType(), restMethod.getUrl());
    logger.info(message);
    //Change request logging for allure
    startStep(message,
            String.format("%s %s %s", restMethod.getType(), restMethod.getUrl(), queryparams));
    return message;
}

private void logResponse(RestResponse response, String uuid) {
    String message = String.format("Received response with %s and body: %s", response.getStatus(), response.getBody());
    logger.info(message);
    //Change response logging for allure
    AllureLogger.passStep(message, uuid);
}
@AfterClass
public void clearLogger() {
    LOG_REQUEST = LOG_REQUEST_TEMP;
    LOG_RESPONSE = LOG_RESPONSE_TEMP;
}

JDI Dark logging is defined in the variables:

LOG_REQUEST = RestMethod::logRequest located in the package com.epam.http.requests, logRequest method uses:

LOG_RESPONSE = RestResponse::logResponse located in the package com.epam.http.response, logResponse method uses:

Customized logging

See full example with redefining variables for console logging and allure. Test example in Java

Response data

public ValidatableResponse isOk()
public ValidatableResponse hasErrors()
public ValidatableResponse isStatus(ResponseStatusType type)
public RestResponse assertStatus(ResponseStatus rs)
public ValidatableResponse isEmpty()
public ValidatableResponse assertBody(MapArray<String, Matcher<?>> params)
public ValidatableResponse assertBody(Object[][] params)
public String getFromHtml(String path)
public Response getRaResponse()
public ValidatableResponse assertThat()

public Map<String, String> cookies()
public String cookie(String name)
public Headers headers()
public String header(String name)

public String getBody()
public ResponseStatus getStatus()
public String getContentType() 

RestResponse response = JettyService.getHello.call();
assertThat(response.body, containsString("{\"hello\":\"Hello Scalatra\"}"));
String body = response.getBody();

String hello = JettyService.getHello.call().getRaResponse().jsonPath().getString("hello");

The class com.epam.http.response.RestResponse represents Response data in JDI Dark.

Below are methods that allow to work with response data:

isOk() and hasErrors() - verify that status code is 2** or 4**, respectively.

isStatus(ResponseStatusType type) and assertStatus(ResponseStatus rs) - verify response status type and response status respectively against expected result.

assertBody(Object[][] params) and assertBody(MapArray> params) - allow to verify response body

cookies() and headers() - return response cookies and header values

cookie(String name) and header(String name) - return cookie and header values respectively corresponding to specified names

getFromHtml() - returns html content of the page by provided path

getRaResponse() and assertThat() returns Rest Assured Response and ValidatableResponse, so it is possible to use Rest Assured methods for validating response body.

getBody(), getStatus(), getContentType() - allows to get response body, status and content type. Response body and status can be also retrieved using body and status fields of the RestResponse class.

isEmpty() - verifies that response body is empty.

Headers

JDI Dark supports header addition to Service endpoints using annotations.

Single header can be added with usage of @Header annotation:

@GET("/header")
@Header(name = "Header_name", value = "Header_value2")
public static RestMethod getHeader;

Here's a simple example of adding a header to an endpoint




@GET("/multiHeaderReflect")
@Headers({@Header(name = "Header_name", value = "Header_value"),
@Header(name = "Header_name2", value = "Header_value2")})
public static RestMethod getMultiHeaderReflect;

Multiple headers are also supported through @Headers annotation:





@GET("/header")
@Headers({@Header(name = "Header_name", value = "Header_value"),
@Header(name = "Header_name", value = "Header_value2")})
public static RestMethod getHeader;


@GET("/header")
@Header(name = "some_header")
public static RestMethod getHeaderWithNoValue;


@GET("/multiHeaderReflect")
@Header(name = "MultiValueHeader", value = "Header_value_1", additionalValues = "Header_value_2")
public static RestMethod getMultiValueHeader;

Headers with same name, with no value and with multiple values can be added as well.

@Headers annotation is used to add 2 headers with the same name - "Header_name"



@Header is used in this example to pass a header with name only



@Header is used here to pass a multiple-value header



Methods to add Headers to Request Data.

Headers can be passed as strings, header objects, maps, and even arrays of objects. Headers without value and with multiple values can be added as well.


@Test
public void requestDataAllowsSpecifyingHeaderWithoutServiceObjectMethods() {
    RestResponse response = getMultiHeaderReflect
            .call(headers().add("MyHeader", "TestValue"));
    response.isOk().assertThat().header("MyHeader", equalTo("TestValue"));
}

@Test
public void requestSpecificationAllowsSpecifyingMultiValueHeadersWithoutServiceObjectMethod() {
    RestResponse response = getMultiHeaderReflect
            .call(headers().addAll(new Object[][]{
                    {"MyHeader", "Something"},
                    {"MyHeader", "SomethingElse"}}));
    response.isOk();
    assertThat(response.headers().getValues("MyHeader").size(), is(2));
    assertThat(response.headers().getValues("MyHeader"), hasItems("Something", "SomethingElse"));
}

Method Description Return Type
headers().add(paramName, paramValue) pass name and value of header RequestData
headers().addAll(Object[][] array2D) pass array with header names and values RequestData
headers().addAll(Map map) pass map with header names and values RequestData
headers().addAll(Header... header) pass header objects RequestData
headers().add(String name) pass header name without value RequestData

Cookies

JDI Dark supports addition of Cookies to Service endpoints.

Cookies with no value and with multiple values can be added as well.

@GET("/cookie")
@Cookies({@Cookie(name = "username", value = "John"),
@Cookie(name = "token", value = "1234")})
public static RestMethod getCookie;

@GET("/cookie_with_no_value")
@Cookie(name = "some_cookie")
public static RestMethod getCookieWithNoValueWithCookies;

@GET("/multiCookieRequest")
@Cookie(name = "key1", value = "value1", additionalValues = "value2")
public static RestMethod getMultiCookieWithCookies;

There are methods to add cookies to Request Data. Cookies can be passed as name and value, name and value pairs, maps, and arrays of objects.

Method Description Return Type
cookies().add(paramName, paramValue) pass name and value of cookie RequestData
cookies().addAll(Object[][] array2D) pass array with cookie names and values RequestData
cookies().addAll(Map map) pass map with cookie names and values RequestData
cookies().add(String name) pass cookie name without cookie RequestData
cookies().add(String name, String... values) pass header name without value RequestData
@GET("/cookie_with_no_value")
public static RestMethod getCookieWithNoValue;

@GET("/cookie")
public static RestMethod getCookie;

@Test
public void requestSpecificationAllowsSpecifyingCookieWithNoValue() {
 RestResponse response = JettyService.getCookieWithNoValue.call(cookies().add("some_cookie"));
 assertThat(response.getBody(), equalTo("some_cookie"));
}

@Test
public void requestSpecificationAllowsSpecifyingCookieUsingMap() {
    Map<String, String> cookies = new LinkedHashMap<>();
    cookies.put("username", "John");
    cookies.put("token", "1234");

    RestResponse response = JettyService.getCookie.call(cookies().addAll(cookies));
    assertThat(response.getBody(), equalTo("username, token"));
}

Proxy

@GET("/greetJSON")
@Proxy(host = "127.0.0.1", port = 8888, scheme = "http")
public static RestMethod getGreenJSONWithProxyParams;

@Test
public void usingProxyAnnotationOnServiceLayer() {
    final Map<String, String> params = new HashMap<>();
    params.put("firstName", "John");
    params.put("lastName", "Doe");
    JettyService.getGreenJSONWithProxyParams.call(RequestData.requestData(queryParams().addAll(params))
            .isOk().assertThat().
            body("greeting.firstName", equalTo("John")).
            body("greeting.lastName", equalTo("Doe"));
}

JDI Dark supports addition of Proxy parameters (host, port and scheme) to Service endpoints.

You can use the RestMethod class with a @Proxy annotation

There is method to add proxy parameters to Request Data. JDI Dark also supports making request calls with Rest Assured ProxySpecification.

Method Description Return Type
setProxySpec(String scheme, String host, int port) set proxy parameters to request data
@GET("/greetJSON")
public static RestMethod getGreenJSON;

@Test
public void usingProxyWithSetProxySpecification() {
    final Map<String, String> params = new HashMap<>();
    params.put("firstName", "John");
    params.put("lastName", "Doe");
    JettyService.getGreenJSON.call(RequestData.requestData(rd -> {
        rd.setProxySpec("http", "localhost", 8888);
        rd.queryParams.addAll(params);
    })).isOk().assertThat().
            body("greeting.firstName", equalTo("John")).
            body("greeting.lastName", equalTo("Doe"));
}

@Test
public void usingProxySpecification() {
       final Map<String, String> params = new HashMap<>();
        params.put("firstName", "John");
        params.put("lastName", "Doe");
        JettyService.getGreenJSON.call(rd -> {
            rd.queryParamsUpdater().addAll(params);
            rd.setProxySpec(ProxySpecification.host("localhost"));
        }).isOk().assertThat().
                body("greeting.firstName", equalTo("John")).
                body("greeting.lastName", equalTo("Doe"));
}

Authentication

public void basicAuthTest() {
        BasicAuthScheme basicAuth = new BasicAuthScheme();
        basicAuth.setUserName("postman");
        basicAuth.setPassword("password");
        RestResponse resp = postmanAuthBasic.call(auth(basicAuth));
        assertEquals(resp.status.code, HttpStatus.SC_OK);
    }

public class OauthCustomAuthScheme extends DataClass<OauthCustomAuthScheme> implements AuthenticationScheme {
    public String consumerKey, signatureMethod, timestamp, nonce, version, signature;

    @Override
    public void authenticate(HTTPBuilder httpBuilder) {
        httpBuilder.getClient().addRequestInterceptor(
                (request, context) ->
                        request.addHeader("Authorization", "OAuth oauth_consumer_key="+ consumerKey 
                +",oauth_signature_method="
                + signatureMethod +",oauth_timestamp="
                + timestamp +",oauth_nonce="+ nonce +",oauth_version="
                + version +",oauth_signature="+ signature));
    }

@BeforeClass
public void before() {
    BasicAuthScheme authScheme = new BasicAuthScheme();
    authScheme.setUserName("postman");
    authScheme.setPassword("password");
    init(AuthorizationPostman.class, authScheme);
    }
}

Perform authentication using AuthenticationScheme interface. The following authentication methods have been implemented: Basic, Digest, NTLM, OAuth1, OAuth2, Form, Certificate.

In order to create a custom authentication scheme implement AuthenticationScheme interface realising authenticate() method.

Authentication can be instantiated on service level. To do so just put your authentication scheme into the service's init() function.
If you put your authentication scheme on the test level, the service level authentication will be overwritten.

Parallel running

 <?xml version="1.0" encoding="WINDOWS-1251"?>
 <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
 <suite name="Http tests" data-provider-thread-count="3">
     <test name="parallel classes running" parallel="classes" thread-count="4">
         <classes>
             <class name="com.epam.jdi.httptests.examples.custom.ResponseTests"/>
             <class name="com.epam.jdi.httptests.examples.custom.AdvancedValidationTests"/>
         </classes>
     </test>
     <test name="parallel methods running" parallel="methods" thread-count="4">
         <classes>
             <class name="com.epam.jdi.httptests.examples.custom.JSONPostTests"/>
         </classes>
     </test>
 </suite>
@DataProvider(name = "createNewBoards", parallel = true)
public static Object[][] createNewBoards() {
    return new Object[][] {
            {generateBoard()},
            {generateBoard()},
            {generateBoard()}
    };
}

@Test(dataProvider = "createNewBoards", threadPoolSize = 3)
public void createCardInBoard(Board board) {
    //Crеate board
    Board createdBoard = service.createBoard(board);
    Board gotBoard = service.getBoard(createdBoard.id);
    assertEquals(gotBoard.name, createdBoard.name, "Name of created board is incorrect");   
}

Here is an example of the parallel tests running based on the TestNG approach.

Setting TestNG xml suite file for the parallel tests running

  1. Specify the parallel attribute in the tag suite or test
    Set parallel="methods" to run test methods in separate threads
    Set parallel="classes" to run test classes in separate threads
  2. Specify the number of threads allocated for the execution in the attribute thread-count
  3. For Data Providers parallel running specify the count of threads in the data-provider-thread-count attribute of the tag suite

Setting Data Provider for the parallel running directly in the test

  1. Add the attribute parallel = true to the @DataProvider annotation. (By default the count of threads = 10)
  2. Specify the count of threads in the attribute threadPoolSize in @DataProvider annotation

Parallel suite example

Read more about TestNG parallel running

Performance testing

public static PerformanceResult loadService(int concurrentThreads, long liveTimeInSec, RestMethod... requests)
public static PerformanceResult loadService(int concurrentThreads, long liveTimeInSec, Map<RestMethod, Integer> weightRequests)
public static PerformanceResult loadService(long liveTimeInSec, RestMethod... requests)
public static PerformanceResult loadService(long liveTimeInSec, Map<RestMethod, Integer> weightRequests)

JDI Dark supports simple performance testing. Use loadService() function to provide the load you need for your performance tests.
The loadService() parameters:
- int concurrentThreads - number of threads
- long liveTimeInSec - test running time
- RestMethod... requests - rest method or methods
- Map weightRequests - rest methods with weights. The weight affects the share of the particular method in the total number of launches.

JDI Dark supports measuring of the minimum, maximum, average response time, number of client's fails, number of server's fails.

Reporting

If you want to use Allure framework for reporting, JDI Dark has out-of-the-box support for generating attachments for Allure reports. Simply install Allure as you would normally do, and those attachments will be automatically added to your reports.

All tests execution results are in the base_directory/allure-results folder.

If you use maven, then you will need to configure the Allure maven plugin to fetch results from a custom folder.

<configuration>
    <resultsDirectory>${basedir}/allure-results</resultsDirectory>
</configuration>

After generating allure results, the full request and response information will be attached to every test executed.

Here is an example of an Allure report with request body attached:

Allure Request

Retry Request Option

@GET(value = "503")
@RetryOnFailure(numberOfRetryAttempts = 2, delay = 1, unit = TimeUnit.SECONDS)
public static RestMethod get503;

Annotation @RetryOnFailure created for resending request when the service responds with one of the statuses you define.

For example, if status codes 502 - more likely it's temporary server-side issue and retrying request will give successful result.

@GET(value = "451")
@RetryOnFailure(numberOfRetryAttempts = 6, errorCodes = 451,delay = 15, unit = TimeUnit.NANOSECONDS)
public static RestMethod get451;

Annotation has parameters to specify:

  1. numberOfRetryAttempts - how many times retry request after failing one.
  2. errorCodes - on which status codes retry request.
  3. delay - after what time to repeat the request.
  4. unit - time unit of delay time
@RetryOnFailure
@ServiceDomain(value = "http://localhost:8080/")
public class RetryingService {

    @GET
    public static RestMethod get502;

    @GET(value = "501")
    public static RestMethod get501;
}

@RetryOnFailure can be applied to class and to field:

JDI Dark will merge annotations data if it's were placed in both places - so you need to specify annotation data only if you want to change some parameters to specific endpoint.

Default annotation param values are:

  1. numberOfRetryAttempts - 3
  2. errorCodes - {502,503}
  3. delay - 10
  4. unit - TimeUnit.MICROSECONDS
@GET(value = "502")
@IgnoreRetry
public static RestMethod ignoreRetrying;

@RetryOnFailure placed on class can be ignored by adding @IgnoreRetry to field:

In that case even if status code will be in specified list of errorCodes no retry requests will be send.

Access RestAssured

Accessing RestAssured.config

Sometime, for testing purposes, some RestAssured configuration properties might be changed. You can access RestAssured from test or define desired properties in @BeforeTest.

In the code snippet we have a test example using default headers set in RestAssured config. In order to restore the initial RestAssured config after test execution, the reset() method is called.

For configuring one request settings see Request data - RequestSpecification config

    public void followsRedirectsWhileKeepingHeadersSpecifiedIfRestAssuredConfig() throws Exception {
        final List<Header> httpClientHeaders = new ArrayList<Header>();
        httpClientHeaders.add(new BasicHeader("header1", "value1"));
        httpClientHeaders.add(new BasicHeader("header2", "value2"));
        RestAssured.config = RestAssuredConfig.newConfig().httpClient(HttpClientConfig
                .httpClientConfig().setParam(DEFAULT_HEADERS, httpClientHeaders));
        RestResponse response = getRedirect.call(requestQueryParams("url", "multiHeaderReflect"));
        response.isOk();
        response.assertThat().header("header1", equalTo("value1"))
                .header("header2", equalTo("value2"));
        RestAssured.reset();
    }

JDI Dark BDD Steps

JDI Dark supports writing tests in BDD style. Create your tests using the following steps:

Feature: Request headers check

  Scenario: Pass headers and check response
    Given init service example
    When set request headers
      | Name | Katarina |
      | Id   | 1        |
    And perform 'get' request
    And print response
    Then response status type is OK
    And response parameter 'headers.Name' is 'Katarina'
    And response parameter 'headers.Id' is '1'

Actions

When perform "<METHOD>" request
When set request content type to "<CONTENT-TYPE>"
When perform "<METHOD NAME>" request with named path parameters "<PATH PARAMETERS>"
When perform "<METHOD NAME>" request with query parameters "<QUERY PARAMETERS>"
When set request headers:
|<GHERKIN DATA TABLE>|
When print response
When load service for "<SECONDS>" seconds with "<METHOD NAME>" request
When print number of performance results requests

Validations

Then "<METHOD NAME>" method is alive Then response status code is "<STATUS CODE>"
Then response body is empty
Then response status type is "<TYPE>"
Then response parameter "<PARAMETER>" is "<VALUE>"
Then response parameter "<PARAMETER>" contains "<VALUE>"
Then response body has values:
|<GHERKIN DATA TABLE>|
Then the average response time is less than "<SECONDS>" seconds
Then response header "<HEADER>" is "<VALUE>"
Then performance result doesn't have any fails

See more information in the Tutorial.
See Cucumber examples here.

WebSockets

Use JDI Dark module jdi-dark-ws for working with websockets.

JDI Dark methods for WebSockets

For working with sockets import and use WebSocketJsonClient, WebSocketTextClient classes or create your own client class, extending and parametrizing WebSocketGenericEndpoint class and passing your own javax.websocket.Decoder and Encoder classes.

These are available methods of WebSocketGenericEndpoint class for working with sockets:

public class WebSocketClientTests extends WithJettyWebSockets {

        @Test
        public void textMessageTest()
                throws DeploymentException, IOException, URISyntaxException, InterruptedException
        {
            String message = "Simple text test message";
            WebSocketTextClient client = new WebSocketTextClient();

            client.connect(host + "/echo-ws");
            client.sendPlainText(message);
            assertTrue(client.waitNewMessage(100));
            assertEquals(
                    client.getLastMessage(), message,
                    "Unexpected response from server"
            );
            client.close();
        }
}
Method Description Return Type
connect(URI path) connect to the URI void
connect(String path) connect to specified path void
close() close the session void
setClientProperties(Map properties) set properties to Tyrus client manager void
setClientProperties(Properties properties) set properties to Tyrus client manager void
setClientSslConfig(String trustStorePath, String trustStorePassword, Boolean clientMode, Boolean needClientAuth, Boolean wantClientAuth) configure ssl for ws client void
sendPlainText(String text) send the web-socket message as a String void
sendMessage(T message) send the web-socket message as object of parametrized type T (you should implement javax.websocket.Decoder and javax.websocket.Encoder for that) void
sendBinary(ByteBuffer data) send the web-socket message as a binary data void
waitNewMessage(int millis) wait for the message for time period specified in milliseconds boolean
waitAndGetNewMessage(int millis) wait for the message and return it parametrized type T
waitNewMessages(int count, int millis) wait for the messages boolean
getLastMessage() get the last received message parametrized type T
getMessages() get Queue with received messages Queue
clearMessages() clear the received messages void
@ClientEndpoint(
        decoders = ItemDecoder.class,
        encoders = ItemEncoder.class
)
public class WSItemClient extends WebSocketGenericEndpoint<Item> {

        @OnMessage
        public void onMessage(Item message, Session session) {
            logger.info("Received message");
            lastMessage = message;
            messages.add(lastMessage);
            latch.countDown();
        }
}

public class WebSocketClientTests extends WithJettyWebSockets {

        @Test
        public void sendObjectGenericTest()
                throws DeploymentException, IOException, URISyntaxException, InterruptedException, EncodeException
        {
            Item message = new Item(2, "sofa");
            WSItemClient client = new WSItemClient();

            client.connect(host + "/item-ws");
            client.sendMessage(message);
            assertTrue(client.waitNewMessage(100));
            assertEquals(
                    client.waitAndGetNewMessage(100), message,
                    "Unexpected response from server"
            );
            client.close();
        }
}

WebSocketJsonClient class, in addition, has following methods:

Method Description Return Type
waitNewMessageMatches(String regex, int maxMsgCount, int sec) wait the message which matches regexp void
waitNewMessageContainsText(String text, int maxMsgCount, int sec) wait the message which contains the specified text void
waitNewMessageContainsKey(String key, int maxMsgCount, int sec) wait the message which contains the specified JSON key void
getLastMessageAsJsonObject() get the message as Object JsonObject

See test examples here

SOAP

Creating Service Object

@ServiceDomain("https://geoservices.tamu.edu/Services/Geocode/WebService/GeocoderService_V04_01.asmx")
@SOAPNamespace("https://geoservices.tamu.edu/")
public class GeoServices {

    @POST()
    @SOAPAction("https://geoservices.tamu.edu/GeocodeAddressNonParsed")
    public static SoapMethod<GeocodeAddressNonParsed, GeocodeAddressNonParsedResponse> geocodeAddressNonParsed;

    @POST()
    @SOAP12
    public static SoapMethod<GeocodeAddressNonParsed, GeocodeAddressNonParsedResponse> geocodeAddressNonParsed12;
}

Available annotations for Service Object:

Annotation Description
@ServiceDomain("value") represents the domain name
@POST() represents HTTP post method
@URL("value") represents URL for SOAP request
@SOAPNamespace("value") represents SOAP namespace
@SOAP12 represents SOAP 1.2
@SOAPAction("value") add SOAPAction header to the SOAP request

See service examples here.

Creating Objects

Generate objects from your WSDL scheme using JDI Dark generator.

JDI Dark Methods for SOAP

For working with SOAP use SoapMethod <T, S> class in package com.epam.http.requests, where T - class describing the body of sending message, S - class describing body of the received message.

Available methods for SoapMethod:

Method Description Return Type
callSoap(T object) makes SOAP request and get SOAP answer S Object
withSoapHeader(Object header) adds header to the SOAP message SoapMethod <T, S> object

Creating tests

public class GeoServicesTests {

    @BeforeTest
    public void before() {
        init(GeoServices.class);
    }

    @Test
    public void negativeGeocodeAddressNonParsed() {
        GeocodeAddressNonParsedResponse response = GeoServices.geocodeAddressNonParsed.callSoap(new GeocodeAddressNonParsed()
                .withStreetAddress("9355 Burton Way")
                .withCity("Beverly Hills")
                .withState("ca")
                .withZip("90210")
                .withApiKey("wrong")
                .withVersion(4.01)
                .withShouldCalculateCensus(true)
                .withCensusYear(CensusYear.ALL_AVAILABLE)
                .withShouldNotStoreTransactionDetails(false)
        );
        Assertions.assertThat(response.getGeocodeAddressNonParsedResult().getQueryStatusCodes()).isEqualTo(QueryStatusCodes.API_KEY_INVALID);
    }

}

Service object class can be initialized in tests. Fields of initialized object can be used to send requests from tests.

See test examples here.

Review Guide

Easy way to pass review

Short checklist

  1. Branch
  2. Label
  3. Codacy
  4. *Other checks
  5. Modified files

Detailed list

  1. Codacy check should pass. If it doesn't, you need to check what's wrong.
  2. *When Travis will be fixed
  3. Also, while reviewing files, you can mark them with checkboxes. The following should be checked:
    • Code style
    • Understandable naming
    • Unnecessary empty lines and spaces
    • Unnecessary or obsolete comments
    • Chaining should be separated by lines
  1. Intellij IDEA
  2. Git

Using libraries

  1. Log library: slf4j + logback-classic (TO_VERIFY)
  2. Test runner: TestNG (TO_VERIFY)
  3. Reporter: Allure Framework (TO_VERIFY)
  4. Test framework: JDI (TO_VERIFY)

Intellij IDEA Preferences

  1. Preferences -> Version Control -> Commit Dialog > Before commit block, check boxes: Reformat Code, Rearrange code, Optimize imports
  2. Inside the app press hotkeys: Ctrl+Shift+Alt+L (Cmd+Shift+Alt+L for MAC), check boxes: Optimize imports, Rearrange Code
  3. When you work on applying autoreformatting, use (for file / directory) Ctrl-Alt-L combination
  4. To open any class instantly by its name, use Ctrl+N (Cmd+O for MAC)
  5. Be sure that the following are NOT checked: Settings -> Editor -> Code Style -> Java -> Tabs and Indents -> Use tab character
  6. Be sure that the following equals to 4: Settings -> Editor -> Code Style -> Java -> Tabs and Indents -> Tab size field and Indent field
  7. Turn off wildcard imports: set the following to 999: Settings -> Editor -> Code style -> Java -> Imports -> Class count to use imports with * field and Names count to use static imports with *

CVS section

  1. Use Git
    • Name branches with useful names
  2. Merge code into any branch only through GitHub pull request (PR)
    • Name PR with useful name
  3. Before adding reviewers to your PR, make sure:
    • there are no codacy issues
    • your code is ready for review
    • you have pushed latest changes
    • you have merged latest changes from your target branch
    • there are no merge conflicts
    • you have addressed all review comments (if applicable)
  4. Add DarkReviewTeam as reviewers or re-request review (if there are already reviewers in PR)
  5. When you get a request for review changes you should resolve all issues and re-request code review from the same reviewers
  6. Repeat until branch is merged by reviewers
  7. Keep your .gitignore file up-to-date

Code style

  1. Use spaces, 1 tab size equals to 4
  2. Don't use wildcard imports
  3. Use Intellij's autoreformatting, import optimization and rearrange option
  4. Fix all Codacy issues
  5. If Codacy issue shouldn't be resolved, use warning suppression
  6. Use semantic names for classes , variables, methods and so on. Use full versions of words (e.g. human instead of h and so on). Common abbreviations (such as id, uid and so on are allowed). Use meaningful names, for example instead of accountList use accounts and so on.
  7. Remove double empty lines, use empty lines only when it makes sense (in addition to to autoreformatting)
  8. Use line breaks correctly: (PROVIDE_SAMPLES)
  9. Limit line width to 100-120 symbols (including indents)
  10. Use chaining style, recommended sample: java object .method(1) .otherMethod();
  11. Always use {} braces even for empty if, for, etc.
  12. Follow the sequence of keywords: public protected private abstract default static final transient volatile synchronized native strictfp
  13. Don't use conveniences such as mValue, _value and alike. __ is acceptable as an unused argument placeholder of test method data provider.
  14. Feel free to smart refactor your code.
  15. Feel free to use ?:
  16. Use UpperCamelCase naming style for classes, lowerCamelCase for fields and methods, UPPERCASE for enums, lowercase for packages
  17. Always use @Override annotation
  18. Don't leave catch{} blocks empty, instead use relevant exceptions with messages
  19. Try to not use global variables
  20. Use variables for integer, string and other values (for instance, int piNumber=3.14; println(piNumber))
  21. Write comments in English
  22. It's better to write code than create a task to write code
  23. It's better to create a task and a TODO with this task number than to simply write TODO in code
  24. It's better to write a simple understandable TODO in code than to tell someone about a problem
  25. It's better to tell someone about a problem than to not take action at all
  26. Always end your files with a newline

JDI Dark Generator

REST objects generation from Swagger

You can generate automatically maven project with service object classes, model classes and test classes based on a swagger scheme.

Algorithm

  1. Clone JDI Dark generator
  2. Build JDI Dark generator by command mvn clean package
  3. Run generate command for auto-generation.

java -jar DarkGenerator.jar generate [ command-args ]

Command may look like this:

java -jar jdi-dark-rest-generator/target/DarkGenerator.jar generate -i -o -p ``

For example, for the petshop swagger scheme after running the command

java -jar jdi-dark-rest-generator/target/DarkGenerator.jar generate -i https://petstore.swagger.io/v2/swagger.json -o ../sample -p com.petshop

the project with the following structure will be created:
project structure

Building

After cloning the project, you can build it from the source with this command:

mvn clean package

If you don't have maven installed, you may directly use the included maven wrapper, and build with the command:

./mvnw clean package

Execution

java -jar DarkGenerator.jar command [ command-args ]

java -jar DarkGenerator.jar generate - Generate JDI annotated classes from Open API (Swagger) specification

COMMANDS

Generate JDI annotated classes from Open API/Swagger specification

Display help information

GENERATE
SYNOPSIS

java -jar DarkGenerator.jar generate [ { -a | --auth } authorization ] [ --additional-properties additional properties ... ] [ --api-package api package ] [ --artifact-id artifact id ] [ --artifact-version artifact version ] [ { -c | --config } configuration file ] [ --codegen-arguments codegen arguments ... ] [ -D system properties ... ] [ --date-library date library ] [ --disable-examples ] [ --flatten-inline-schema ] [ --git-repo-id git repo id ] [ --git-user-id git user id ] [ --group-id group id ] [ { -h | --help } ] [ --hide-generation-timestamp ] [ --http-user-agent http user agent ] [ { -i | --input-spec } spec file ] [ --ignore-file-override ignore file override location ] [ --ignore-import-mapping ignore import mapping ] [ --import-mappings import mappings ... ] [ --instantiation-types instantiation types ... ] [ --language-specific-primitives language specific primitives ... ] [ --model-name-prefix model name prefix ] [ --model-name-suffix model name suffix ] [ --model-package model package ] [ { -o | --output } output directory ] [ { -p | --invoker-package } invoker package ] [ --release-note release note ] [ --remove-operation-id-prefix ] [ --reserved-words-mappings reserved word mappings ... ] [ { -s | --skip-overwrite } ] [ --serialization-library serialization library ] [ --skip-alias-generation ] [ { -t | --template-dir } template directory ] [ --template-engine template engine ] [ --type-mappings type mappings ... ] [ --use-oas2 ] [ { -v | --verbose } ]

OPTIONS

It is obligatory to specify input specification either as command line option or in supplied config file. Other options are not required and have reasonable defaults.

adds authorization headers when fetching the swagger definitions remotely. Pass in a URL-encoded string of name:header with a comma separating multiple values

sets additional properties that can be referenced by the mustache templates in the format of name=value,name=value. You can also have multiple occurrences of this option.

package for generated api classes

artifactId in generated pom.xml

artifact version in generated pom.xml

Path to json configuration file. File content should be in a json format {"optionKey":"optionValue", "optionKey1":"optionValue1"...} Supported options can be different for each language. Run config-help -l {lang} command for language specific config options.

codegen arguments

sets specified system properties in the format of name=value,name=value (or multiple options, each with name=value)

Option. Date library to use . joda - Joda (for legacy app only) . legacy - Legacy java.util.Date (if you really have a good reason not to use threetenbp . java8-localdatetime - Java 8 using LocalDateTime (for legacy app only) . java8 - Java 8 native JSR310 (preferred for jdk 1.8+) - note: this also sets "java8" to true . threetenbp - Backport of JSR310 (preferred for jdk < 1.8) . Default Java 8

disable examples

flattenInlineSchema

Git repo ID, e.g. swagger-codegen.

Git user ID, e.g. swagger-api.

groupId in generated pom.xml

Display help information

Hides the generation timestamp when files are generated.

HTTP user agent, e.g. codegen_csharp_api_client, default to 'Swagger-Codegen/{packageVersion}}/{language}'

location of the swagger spec, as URL or file (required)

Specifies an override location for the .swagger-codegen-ignore file. Most useful on initial generation.

allow generate model classes using names previously listed on import mappings.

specifies mappings between a given class and the import that should be used for that class in the format of type=import,type=import. You can also have multiple occurrences of this option.

sets instantiation type mappings in the format of type=instantiatedType,type=instantiatedType.For example (in Java): array=ArrayList,map=HashMap. In other words array types will get instantiated as ArrayList in generated code. You can also have multiple occurrences of this option.

specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. You can also have multiple occurrences of this option.

Prefix that will be prepended to all model names. Default is the empty string.

Suffix that will be appended to all model names. Default is the empty string.

package for generated models

where to write the generated files. Defaults to generated-code folder

root package for generated code

Release note, default to 'Minor update'.

Remove prefix of operationId, e.g. config_getId => getId

specifies how a reserved name should be escaped to. Otherwise, the default _ is used. For example id=identifier. You can also have multiple occurrences of this option.

specifies if the existing files should be overwritten during the generation.

Serialization library (Default: Jackson)

skip code generation for models identified as alias.

folder containing the template files

template engine

sets mappings between swagger spec types and generated code types in the format of swaggerType=generatedType,swaggerType=generatedType. For example: array=List,map=Map,string=String. You can also have multiple occurrences of this option.

use OpenAPI v2.0 (Swagger 1.5.x)

verbose mode


HELP

java -jar DarkGenerator.jar help - Display help information

SYNOPSIS

java -jar DarkGenerator.jar help [ -- ] [ command ]

OPTIONS

This option can be used to separate command-line options from the list of arguments (useful when arguments might be mistaken for command-line options)

Documentation

Documentation can be generated with running following command:

mvn airline:generate

Documentation will appear in target/help folder.

SOAP objects generation from WSDL

You can generate automatically JDI Dark SOAP object classes based on your WSDL scheme.

  1. Clone JDI Dark generator
  2. Build JDI Dark generator by command mvn clean package
  3. Modify pom.xml in jdi-dark-soap-generator:

    • set wsdlUrls, for example:
      <wsdlUrls> <wsdlUrl>https://speller.yandex.net/services/spellservice?WSDL</wsdlUrl> </wsdlUrls>
    • set the package name, for example:
      <packageName>com.epam.jdi.soap.net.yandex.speller.services.spellservice</packageName>
  4. Run maven plugin jaxws:wsimport

The project with the following structure is created:
project structure

Note: A few excessive files are autogenerated. Use service objects' files for creating JDI Dark SOAP methods.