Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to fast generate your API Test with OpenAPI Tools and Rest-Assured

How to fast generate your API Test with OpenAPI Tools and Rest-Assured

The creation of API Test Automation using Java and Rest-Assured might be boring because it always follows the same repeated pattern: creating the code to make the HTTP requests, the model classes used in the responses, the object mapping, and the tests.

With the usage of the OpenAPI Tools, we can quickly generate the client code and the test stubs, speeding up the development process.

You will also learn the possible problems during its adoption that can add more maintenance effort and how to solve it with abstractions.

You will learn how to use an OpenAPI specification to automatically generate the client code to use in your test, as we will call it basic usage. As the first improvement, you will learn how to use templates and automatically create custom client and test code. And as the ultimate improvement, you will learn how to decrease the maintenance and focus on a more business-driven development by creating abstractions on top of the client code

Elias Nogueira

February 04, 2023
Tweet

More Decks by Elias Nogueira

Other Decks in Technology

Transcript

  1. How to fast generate your
    API Test with OpenAPI Tools
    and Rest-Assured
    Elias Nogueira

    View Slide

  2. @eliasnogueira

    View Slide

  3. context and agreements

    View Slide

  4. the problems to solve

    View Slide

  5. the problems to solve
    ● cleaner code
    ● low test maintainability
    ● test association within the API spec
    ● easy way to share code between services
    eliasnogueira.com

    View Slide

  6. the backend application

    View Slide

  7. The backend is composed of two services:
    Simulations API
    CRUD operation to record a loan simulation
    Restrictions API
    GET endpoint to know if a consumer has a restriction, if yes, a simulation is
    not allowed.
    the backend application
    eliasnogueira.com

    View Slide

  8. the backend application
    eliasnogueira.com
    POST /simulations
    GET /restrictions
    has
    restriction?
    HTTP 403
    Forbidden
    Restrictions Simulations

    View Slide

  9. the (non-existent) frontend
    eliasnogueira.com

    View Slide

  10. API documentation

    View Slide

  11. api documentation
    eliasnogueira.com

    View Slide

  12. REST Assured

    View Slide

  13. REST Assured
    http://rest-assured.io
    Java DSL for simplifying testing of REST based services.
    It has an intuitive code syntax to create an automated test.
    Let’s express it in a natural language:
    Given I have a parameter to send
    When I send a request using an HTTP Method an URL
    Then I can validate the status code and response body
    NOTE: this is not a BDD approach and I have no intention to apply it
    eliasnogueira.com

    View Slide

  14. REST Assured
    http://rest-assured.io
    REST Assured uses the same keywords given, when, then to express it
    import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    void welcomeTest() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    eliasnogueira.com

    View Slide

  15. import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    void welcomeTest() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    REST Assured
    http://rest-assured.io
    import libraries
    REST Assured uses the same keywords given, when, then to express it
    eliasnogueira.com

    View Slide

  16. import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    void welcomeTest() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    REST Assured
    http://rest-assured.io
    test method and annotation from JUnit 5
    REST Assured uses the same keywords given, when, then to express it
    eliasnogueira.com

    View Slide

  17. import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    void welcomeTest() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    REST Assured
    http://rest-assured.io
    request pre-condition
    REST Assured uses the same keywords given, when, then to express it
    eliasnogueira.com

    View Slide

  18. import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    public void welcome() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    REST Assured
    http://rest-assured.io
    action (request)
    REST Assured uses the same keywords given, when, then to express it
    eliasnogueira.com

    View Slide

  19. import static io.restassured.RestAssured.*;
    import static org.hamcrest.Matchers.*;
    class RestAssuredExampleTest {
    @Test
    void welcomeTest() {
    given().
    param("name", "Elias").
    when().
    post("/register").
    then().
    statusCode(200).
    body("message", is("Hello Elias"));
    }
    }
    REST Assured
    http://rest-assured.io
    assert the response body
    REST Assured uses the same keywords given, when, then to express it
    eliasnogueira.com

    View Slide

  20. test using REST Assured

    View Slide

  21. tests using REST Assured
    Raw REST Assured tests are the entry point when we start the test
    eliasnogueira.com
    @Test
    void shouldCreateNewSimulation() {
    var simulation = Simulation.builder().name("John").cpf("4750403823739")
    .email("[email protected]").amount(new BigDecimal("10.0000"))
    .installments(8).insurance(true).build();
    given()
    .body(simulation)
    .contentType(ContentType.JSON)
    .when()
    .post("/simulations/")
    .then().log().all()
    .statusCode(HttpStatus.SC_CREATED)
    .header("Location", CoreMatchers.containsString(simulation.getCpf()));
    }

    View Slide

  22. tests using REST Assured
    Raw REST Assured tests are the entry point when we start the test
    eliasnogueira.com
    @Test
    void shouldUpdateExistingSimulation() {
    String existingCpf = "17822386034";
    var simulation = Simulation.builder().name("Elias").cpf("17822386034")
    .email("[email protected]").amount(new BigDecimal("3000.00"))
    .installments(5).insurance(true).build();
    var simulationUpdated =
    given()
    .pathParam("cpf", existingCpf)
    .body(simulation)
    .contentType(ContentType.JSON)
    .when()
    .put("/simulations/{cpf}")
    .then()
    .statusCode(HttpStatus.SC_OK)
    .extract().as(Simulation.class);
    assertThat(simulationUpdated).isEqualTo(simulation);
    }

    View Slide

  23. show me the code

    View Slide

  24. REST Assured
    But there’s one problem: the test do not scale as the application evolves!
    We will end up with a lot of duplications.
    The possible, logical, solution, is the usage of the Request and Response
    Specifications
    eliasnogueira.com

    View Slide

  25. tests using req-resp specification
    eliasnogueira.com
    @Test
    void shouldCreateNewSimulation() {
    var simulation = Simulation.builder().name("John").cpf("4750403823739")
    .email("[email protected]").amount(new BigDecimal("10.0000"))
    .installments(8).insurance(true).build();
    given()
    .spec(SimulationsSpecifications.bodyParam(simulation))
    .when()
    .post("/simulations/")
    .then()
    .spec(SimulationsSpecifications.location(simulation.getCpf()));
    }

    View Slide

  26. tests using req-resp specification
    eliasnogueira.com
    @Test
    void shouldCreateNewSimulation() {
    var simulation = Simulation.builder().name("John").cpf("4750403823739")
    .email("[email protected]").amount(new BigDecimal("10.0000"))
    .installments(8).insurance(true).build();
    given()
    .spec(SimulationsSpecifications.bodyParam(simulation))
    .when()
    .post("/simulations/")
    .then()
    .spec(SimulationsSpecifications.location(simulation.getCpf()));
    }
    request specification
    response specification

    View Slide

  27. tests using req-resp specification
    eliasnogueira.com
    public final class SimulationsSpecifications {
    private SimulationsSpecifications() {}
    public static RequestSpecification bodyParam(Simulation simulation) {
    return new RequestSpecBuilder().setBody(simulation).setContentType(ContentType.JSON).build();
    }
    public static ResponseSpecification location(String cpf) {
    return new ResponseSpecBuilder().expectStatusCode(SC_CREATED)
    .expectHeader("Location", CoreMatchers.containsString(cpf)).build();
    }
    }

    View Slide

  28. tests using req-resp specification
    eliasnogueira.com
    public final class SimulationsSpecifications {
    private SimulationsSpecifications() {}
    public static RequestSpecification bodyParam(Simulation simulation) {
    return new RequestSpecBuilder().setBody(simulation).setContentType(ContentType.JSON).build();
    }
    public static ResponseSpecification location(String cpf) {
    return new ResponseSpecBuilder().expectStatusCode(SC_CREATED)
    .expectHeader("Location", CoreMatchers.containsString(cpf)).build();
    }
    }
    build the request specification

    View Slide

  29. tests using req-resp specification
    eliasnogueira.com
    public final class SimulationsSpecifications {
    private SimulationsSpecifications() {}
    public static RequestSpecification bodyParam(Simulation simulation) {
    return new RequestSpecBuilder().setBody(simulation).setContentType(ContentType.JSON).build();
    }
    public static ResponseSpecification location(String cpf) {
    return new ResponseSpecBuilder().expectStatusCode(SC_CREATED)
    .expectHeader("Location", CoreMatchers.containsString(cpf)).build();
    }
    }
    build the response specification

    View Slide

  30. show me the code

    View Slide

  31. api-spec

    View Slide

  32. api-spec
    eliasnogueira.com
    openapi: 3.0.0
    "/api/v1/restrictions/{cpf}":
    get:
    tags:
    - Restrictions
    summary: Query to search for a restricted CPF
    operationId: oneV2UsingGET
    parameters:
    - name: cpf
    in: path
    description: CPF to query
    required: true
    schema:
    type: string
    responses:
    "200":
    description: Restriction found
    content:
    "application/json":
    schema:
    $ref: "#/components/schemas/MessageV1"
    "404":
    description: No restrictions

    View Slide

  33. api-spec
    You have noticed, so far, we are using the “raw” REST Assured usage through
    the methods given(), when(), and then().
    They are a great start, but it won’t scale as the API and test grow.
    One of the possible approaches is to create an abstraction around the main
    REST Assured methods, having minimal work using the OpenAPI spec.
    eliasnogueira.com

    View Slide

  34. Normally we can also have the file and use it to generate the basic structure
    of the request and response.
    There’re several tools, and we will use the:
    ○ wagon-maven-plugin: to download the spec
    ○ openapi-generator-maven-plugin: to generate the API client
    based on the spec
    eliasnogueira.com
    api-spec

    View Slide

  35. openapi-generator

    View Slide

  36. download the spec file
    The wagon-maven-plugin will help us to download a file to a specific
    directory.
    In general, we will tell the plugin to:
    ○ download a file
    ○ describe the file location
    ○ describe the destination
    eliasnogueira.com

    View Slide

  37. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    View Slide

  38. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    unique id to identify the execution
    action, in case of multiple downloads

    View Slide

  39. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    goal from the plugin

    View Slide

  40. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    Maven Build Lifecycle that
    will trigger this action

    View Slide

  41. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    URL to the file

    View Slide

  42. download the spec file
    eliasnogueira.com

    org.codehaus.mojo
    wagon-maven-plugin
    ${wagon-maven-plugin.version}


    download-credit-api-spec

    download-single

    generate-sources


    URL-TO-THE-FILE-INCLUDING-FILE-NAME-AND-EXTENSION

    ${project.basedir}/target/openapiSpecs




    file internal location

    View Slide

  43. show me the code

    View Slide

  44. generate the Client API
    The openapi-generator-maven-plugin will help us to generate the
    Client API and it models based on the Open API file specification.
    In general, we will tell the plugin to:
    ○ look at a specific folder to know the spec file
    ○ define the main, api and model packages
    ○ use REST Assured as a support library
    ○ set the serialization library
    eliasnogueira.com

    View Slide

  45. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    View Slide



  46. generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    generate the Client API
    eliasnogueira.com
    unique id in case of many generator

    View Slide

  47. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    goal to generate the code

    View Slide

  48. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    Maven Lifecycle phase that
    will trigger the execution

    View Slide

  49. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    Open API spec file location

    View Slide

  50. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    the package used for the generated
    invoker (common) objects

    View Slide

  51. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    the package used for the
    generated client api

    View Slide

  52. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    the package used for the
    generated models

    View Slide

  53. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    other configurations

    View Slide

  54. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    library used in the client api

    View Slide

  55. generate the Client API
    eliasnogueira.com


    generate-client-api-code

    generate

    generate-sources


    ${project.build.directory}/openapiSpecs/credit-api.yaml

    com.eliasnogueira.credit.invoker
    com.eliasnogueira.credit.api
    com.eliasnogueira.credit.model
    java
    false
    false

    rest-assured
    jackson




    serialization library

    View Slide

  56. show me the code

    View Slide

  57. auto-generated tests

    View Slide

  58. auto-generated tests
    We can set the configuration to true to generate the
    API Tests using the OpenAPI Generator, but the tests are incomplete.
    It makes sense because the generator doesn’t know:
    ● the validation we must do
    ● The parameter we need to use
    The approach using the auto-generated tests isn’t that good L
    eliasnogueira.com

    View Slide

  59. auto-generated tests
    eliasnogueira.com
    public class RestrictionsApiTest {
    private RestrictionsApi api;
    @Before
    public void createApi() {
    api = ApiClient.api(ApiClient.Config.apiConfig().reqSpecSupplier(
    () -> new RequestSpecBuilder()
    .setConfig(config().objectMapperConfig(objectMapperConfig().defaultObjectMapper(jackson())))
    .addFilter(new ErrorLoggingFilter())
    .setBaseUri("http://localhost:8088"))).restrictions();
    }
    /**
    * Restriction found
    */
    @Test
    public void shouldSee200AfterOneUsingGET() {
    String cpf = null;
    api.oneUsingGET()
    .cpfPath(cpf).execute(r -> r.prettyPeek());
    // TODO: test validations
    }
    }

    View Slide

  60. auto-generated tests
    eliasnogueira.com
    public class RestrictionsApiTest {
    private RestrictionsApi api;
    @Before
    public void createApi() {
    api = ApiClient.api(ApiClient.Config.apiConfig().reqSpecSupplier(
    () -> new RequestSpecBuilder()
    .setConfig(config().objectMapperConfig(objectMapperConfig().defaultObjectMapper(jackson())))
    .addFilter(new ErrorLoggingFilter())
    .setBaseUri("http://localhost:8088"))).restrictions();
    }
    /**
    * Restriction found
    */
    @Test
    public void shouldSee200AfterOneUsingGET() {
    String cpf = null;
    api.oneUsingGET()
    .cpfPath(cpf).execute(r -> r.prettyPeek());
    // TODO: test validations
    }
    }
    API creation with hardcoded URI

    View Slide

  61. auto-generated tests
    eliasnogueira.com
    public class RestrictionsApiTest {
    private RestrictionsApi api;
    @Before
    public void createApi() {
    api = ApiClient.api(ApiClient.Config.apiConfig().reqSpecSupplier(
    () -> new RequestSpecBuilder()
    .setConfig(config().objectMapperConfig(objectMapperConfig().defaultObjectMapper(jackson())))
    .addFilter(new ErrorLoggingFilter())
    .setBaseUri("http://localhost:8088"))).restrictions();
    }
    /**
    * Restriction found
    */
    @Test
    public void shouldSee200AfterOneUsingGET() {
    String cpf = null;
    api.oneUsingGET()
    .cpfPath(cpf).execute(r -> r.prettyPeek());
    // TODO: test validations
    }
    }
    test template

    View Slide

  62. show me the code

    View Slide

  63. architecture
    Using the raw features of REST Assured for test creation might not scale as
    we need to solve possible duplication along its creation.
    The changes in the API spec will also influence, increasing the code
    maintainability.
    Thankfully, using abstractions and design patterns we can easily solve these
    problems.
    eliasnogueira.com

    View Slide

  64. architecture
    Proposed architecture
    eliasnogueira.com
    *Test
    *Test
    *Data
    RestApiClientBuilder
    *ApiClient
    *ApiService
    Client API abstraction Service abstraction
    build uses use

    View Slide

  65. architecture
    RestApiClientBuilder
    Each Client API generated by the OpenAPI Generator will carry the full path
    to the related HTTP request.
    SimulationsApi.java
    eliasnogueira.com
    public static class DeleteUsingDELETEOper implements Oper {
    public static final Method REQ_METHOD = DELETE;
    public static final String REQ_URI = "/api/v1/simulations/{cpf}";
    // code ignored

    View Slide

  66. architecture
    RestApiClientBuilder
    The main necessity of this class is to add, to all requests, the baseUri, and the
    port. The basePath is not necessary as the generated client already has it.
    eliasnogueira.com
    public class RestApiClientBuilder {
    public T build(Function, T> clientCreator) {
    Supplier requestSpecBuilderSupplier = () -> new RequestSpecBuilder()
    .addRequestSpecification(
    new RequestSpecBuilder()
    .setBaseUri("http://localhost")
    .setPort(8088)
    .build());
    return clientCreator.apply(requestSpecBuilderSupplier);
    }
    }

    View Slide

  67. architecture

    View Slide

  68. architecture
    *ApiClient
    This will abstract the current Client API class generated by the OpenAPI
    Generator, based on REST Assured, and will use the RestClientApiBuilder
    to add the common request specification.
    The generated Client Api class adds an inner class per HTTP request
    matching the OpenAPI spec.
    eliasnogueira.com

    View Slide

  69. architecture
    eliasnogueira.com
    public class RestrictionsApi {
    public static RestrictionsApi restrictions(Supplier reqSpecSupplier) {
    return new RestrictionsApi(reqSpecSupplier);
    }
    public OneUsingGETOper oneUsingGET() {
    return new OneUsingGETOper(createReqSpec());
    }
    }

    View Slide

  70. architecture
    eliasnogueira.com
    public class RestrictionsApi {
    public static RestrictionsApi restrictions(Supplier reqSpecSupplier) {
    return new RestrictionsApi(reqSpecSupplier);
    }
    public OneUsingGETOper oneUsingGET() {
    return new OneUsingGETOper(createReqSpec());
    }
    }
    Will be used by the RestClientApiBuilder
    to build the Client Api

    View Slide

  71. architecture
    eliasnogueira.com
    public class RestrictionsApi {
    public static RestrictionsApi restrictions(Supplier reqSpecSupplier) {
    return new RestrictionsApi(reqSpecSupplier);
    }
    public OneUsingGETOper oneUsingGET() {
    return new OneUsingGETOper(createReqSpec());
    }
    }
    Inner class with the HTTP method,
    basePath and requests specifics (params)

    View Slide

  72. architecture
    eliasnogueira.com
    public static class OneUsingGETOper implements Oper {
    public static final Method REQ_METHOD = GET;
    public static final String REQ_URI = "/api/v1/restrictions/{cpf}";
    @Override
    public T execute(Function handler) {
    // magic
    }
    public static final String CPF_PATH = "cpf";
    public OneUsingGETOper cpfPath(Object cpf) {
    reqSpec.addPathParam(CPF_PATH, cpf);
    return this;
    }
    }

    View Slide

  73. architecture
    eliasnogueira.com
    public static class OneUsingGETOper implements Oper {
    public static final Method REQ_METHOD = GET;
    public static final String REQ_URI = "/api/v1/restrictions/{cpf}";
    @Override
    public T execute(Function handler) {
    // magic
    }
    public static final String CPF_PATH = "cpf";
    public OneUsingGETOper cpfPath(Object cpf) {
    reqSpec.addPathParam(CPF_PATH, cpf);
    return this;
    }
    }
    HTTP method and baseUri

    View Slide

  74. architecture
    eliasnogueira.com
    public static class OneUsingGETOper implements Oper {
    public static final Method REQ_METHOD = GET;
    public static final String REQ_URI = "/api/v1/restrictions/{cpf}";
    @Override
    public T execute(Function handler) {
    // magic
    }
    public static final String CPF_PATH = "cpf";
    public OneUsingGETOper cpfPath(Object cpf) {
    reqSpec.addPathParam(CPF_PATH, cpf);
    return this;
    }
    }
    adds the HTTP method and baseUri to the request

    View Slide

  75. architecture
    eliasnogueira.com
    public static class OneUsingGETOper implements Oper {
    public static final Method REQ_METHOD = GET;
    public static final String REQ_URI = "/api/v1/restrictions/{cpf}";
    @Override
    public T execute(Function handler) {
    // magic
    }
    public static final String CPF_PATH = "cpf";
    public OneUsingGETOper cpfPath(Object cpf) {
    reqSpec.addPathParam(CPF_PATH, cpf);
    return this;
    }
    }
    param name and method to add it into the request

    View Slide

  76. architecture
    *ApiClient Creation
    We need to use the RestApiClientBuilder to build the Client Api instance to
    add the URL and port.
    Then we add a method per HTTP request. This is a recommended approach
    to ease any change (even to a different library).
    eliasnogueira.com

    View Slide

  77. architecture
    eliasnogueira.com
    public class RestrictionsApiClient {
    private RestrictionsApi restrictionsApi =
    new RestApiClientBuilder().build(RestrictionsApi::restrictions);
    public Response queryCpf(String cpf) {
    return restrictionsApi.oneUsingGET().cpfPath(cpf).execute(Function.identity());
    }
    }
    build of the Client Api using
    the RestApiClientBuilder

    View Slide

  78. architecture
    eliasnogueira.com
    public class RestrictionsApiClient {
    private RestrictionsApi restrictionsApi =
    new RestApiClientBuilder().build(RestrictionsApi::restrictions);
    public Response queryCpf(String cpf) {
    return restrictionsApi.oneUsingGET().cpfPath(cpf).execute(Function.identity());
    }
    }
    abstracting the internal (ugly) Client Api usage
    - returning a generic response
    - adding a meaningful name
    - adding the path parameter as the method parameter

    View Slide

  79. architecture
    eliasnogueira.com
    public class RestrictionsApiClient {
    private RestrictionsApi restrictionsApi =
    new RestApiClientBuilder().build(RestrictionsApi::restrictions);
    public Response queryCpf(String cpf) {
    return restrictionsApi.oneUsingGET().cpfPath(cpf).execute(Function.identity());
    }
    }
    using the auto-generated
    internal Client Api method

    View Slide

  80. architecture
    eliasnogueira.com
    public class RestrictionsApiClient {
    private RestrictionsApi restrictionsApi =
    new RestApiClientBuilder().build(RestrictionsApi::restrictions);
    public Response queryCpf(String cpf) {
    return restrictionsApi.oneUsingGET().cpfPath(cpf).execute(Function.identity());
    }
    }
    using the path parameter

    View Slide

  81. show me the code

    View Slide

  82. architecture
    *ApiService
    The *ApiService abstraction will use the *ApiClient abstraction to consume
    its methods in different ways. This is the class we will use in the tests.
    The service can have one or multiple actions from the *ApiClient and it can
    be related to the Mediator design pattern, as it encapsulates how a set of
    objects (methods) interact.
    eliasnogueira.com

    View Slide

  83. architecture
    *ApiService - Example
    We do have two test for the Restrictions API:
    ○ Expecting a restriction
    ○ Not expecting a restriction
    We will create the request for both, returning the correct response, in the
    service abstraction.
    eliasnogueira.com

    View Slide

  84. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    }

    View Slide

  85. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    }
    instance of the abstracted Client Api

    View Slide

  86. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    }
    method to query the cpf expecting no restriction

    View Slide

  87. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    }
    returning true because the
    status code is an HTTP 404
    so, we can add an assertions
    in the test

    View Slide

  88. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    }
    method to query the cpf expecting a restriction

    View Slide

  89. architecture
    eliasnogueira.com
    public class RestrictionsApiService {
    private RestrictionsApiClient restrictionsApiClient = new RestrictionsApiClient();
    /**
    * Query CPF without a restriction
    */
    public boolean queryCpf(String cpf) {
    restrictionsApiClient.queryCpf(cpf).then().statusCode(HttpStatus.SC_NOT_FOUND);
    return true;
    }
    public MessageV1 queryCpfWithRestriction(String cpf) {
    return restrictionsApiClient.queryCpf(cpf).then().
    statusCode(HttpStatus.SC_OK).extract().as(MessageV1.class);
    }
    } it returns the expected
    response body

    View Slide

  90. show me the code

    View Slide

  91. architecture
    Test
    Now the tests will be created using only the Service class.
    The different is that we don’t need to use the raw REST Assured methods
    anymore, relying only in the Service.
    The structure of precondition, action and assert will be always present in
    the new way to create tests.
    eliasnogueira.com

    View Slide

  92. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    given()
    .spec(SharedRequestSpecs.cpfPathParameter("62648716050"))
    .when()
    .get("/restrictions/{cpf}")
    .then()
    .statusCode(HttpStatus.SC_OK)
    .body("message", CoreMatchers.is("CPF 62648716050 has a restriction"));
    }
    Raw REST Assured Test

    View Slide

  93. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    given()
    .spec(SharedRequestSpecs.cpfPathParameter("62648716050"))
    .when()
    .get("/restrictions/{cpf}")
    .then()
    .statusCode(HttpStatus.SC_OK)
    .body("message", CoreMatchers.is("CPF 62648716050 has a restriction"));
    }
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }
    Raw REST Assured Test
    REST Assured Test using Client – Service abstraction

    View Slide

  94. architecture
    Test
    The usage of the service abstraction add more readability and help us to
    decrease the maintainability as we will have only one place to change it
    behaviour (client or service).
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }

    View Slide

  95. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }

    View Slide

  96. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }
    instance to use the Service abstraction

    View Slide

  97. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }
    usage of the method in the service

    View Slide

  98. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    } associating the correct return (response body)

    View Slide

  99. architecture
    eliasnogueira.com
    @Test
    void shouldReturnRestriction() {
    RestrictionsApiService restrictionsApiService = new RestrictionsApiService();
    MessageV1 message = restrictionsApiService.queryCpfWithRestriction("60094146012");
    Assertions.assertThat(message.getMessage()).contains("60094146012");
    }
    assertion using the response body object

    View Slide

  100. show me the code

    View Slide

  101. architecture
    Recommendation
    A better approach is to use the RestClientApiBuilder to deal only with the
    global request actions and the BaseApiConfiguration with the
    configurations related to the test.
    Both classes do different things, and they must have a single responsibility.
    eliasnogueira.com

    View Slide

  102. Thank you!
    twitter.com/eliasnogueira
    linkedin.com/in/eliasnogueira

    View Slide