GithubHelp home page GithubHelp logo

serenity-rest-starter's Introduction

Getting started with REST API testing with Serenity and Cucumber 6

This tutorial show you how to get started with REST-API testing using Serenity and Cucumber 6.

Get the code

Git:

git clone https://github.com/serenity-bdd/serenity-rest-starter.git
cd serenity-rest-starter

Or simply download a zip file.

The starter project

The best place to start with Serenity and Cucumber is to clone or download the starter project on Github (https://github.com/serenity-bdd/serenity-rest-starter). This project gives you a basic project setup, along with some sample tests and supporting classes. The starter project comes bundled with a sample SpringBoot web service, and some RestAssured-based tests. The project also illustrates how you might use Freemarker to prepare test data for your scenarios.

The project directory structure

The project has build scripts for both Maven and Gradle, and follows the standard directory structure used in most Serenity projects:

src
  + main
  + test
    + java                                Test runners and supporting code
    + resources
      + features                          Feature files

          + status
          + trades
             record_a_new_trade.feature 
      + templates                         Freemarker templates and properties files                

A simple GET scenario

The project comes with two simple scenarios, one that illustrates a GET, and a second that illustrates a POST.

The first scenario exercises the /api/status endpoint Both variations of the sample project uses the sample Cucumber scenario. In this scenario, Sergey (who likes to search for stuff) is performing a search on the DuckDuckGo search engine:

  Scenario: Application status end-point
    Given the application is running
    When I check the application status
    Then the API should return "Serenity REST Starter project up and running"

The glue code for this scenario illustrates the layered approach we find works well for both web and non-web acceptance tests. The glue code is responsible for orchestrating calls to a layer of more business-focused classes, which perform the actual REST calls.

    @Steps
    ApplicationStatus theApplication;

    @Given("the application is running")
    public void the_application_is_running() {
        assertThat(theApplication.currentStatus()).isEqualTo(RUNNING);
    }

    @When("I check the application status")
    public void i_check_the_application_status() {
        theApplication.readStatusMessage();
    }

The actual REST calls are performed using RestAssured in the action classes, like ApplicationStatus here. These use either RestAssured (if we don't want the queries to appear in the reports) or SerenityRest (if we do):

public class ApplicationStatus {

    public AppStatus currentStatus() {
        int statusCode = RestAssured.get(STATUS.getUrl()).statusCode();
        return (statusCode == 200) ? AppStatus.RUNNING : AppStatus.DOWN;
    }

    @Step("Get current status message")
    public void readStatusMessage() {
        SerenityRest.get(STATUS.getUrl());
    }
}

In steps that perform assertions, we can also use the SerenityRest.restAssuredThat() helper method, which lets us make a RestAssured assertion on the last response the server sent us:

    @Then("the API should return {string}")
    public void the_API_should_return(String expectedMessage) {
        restAssuredThat(lastResponse -> lastResponse.body(equalTo(expectedMessage)));
    }

A more complex scenario

The other sample scenario performs a POST query:

Feature: Record a new trade

  Scenario: Each trade has a unique ID
    Given the following trade:
    | security | buySell | quantity | priceInCents |
    | APPL     | BUY     | 10       | 10000        |
    When I record the trade
    Then the recorded trade should include the following details:
      | security | buySell | quantity | priceInCents | totalCostInCents |
      | APPL     | BUY     | 10       | 10000        | 100000           |

The Given step uses a Freemarker template to merge the data in the Cucumber table with values defined in a properties file - to see how this works in detail, have a look at the MergeFrom class.

    @Given("the following trade:")
    public void the_folowing_trade(List<Map<String, String>> tradeDetails) throws IOException {

        trade = MergeFrom.template("templates/trade.json")
                         .withDefaultValuesFrom(FieldValues.in("templates/standard-trade.properties"))
                         .withFieldsFrom(tradeDetails.get(0));
    }

Once the message to be posted has been prepared, we use another action class (tradingSystem) to perform the post:

    @Steps
    RecordNewTrade recordNewTrade;
   
    @When("I record the trade")
    public void i_record_the_trade() {
        recordNewTrade.withDetails(trade);
    }

The RecordNewTrade class is responsible for posting this query to the end point, as shown below:

public class RecordNewTrade {

    @Step("Record a new trade")
    public void withDetails(String trade) {
        SerenityRest.given()
                .contentType("application/json")
                .header("Content-Type", "application/json")
                .body(trade)
                .when()
                .post(WebServiceEndPoints.TRADE.getUrl());
    }
}

The last step checks that the total cost has been recorded correctly in the trade. In a real project, this would typically be implemented via another REST call or by a database query, but here we are illustrating how we can get and compare tabular data from JSON responses.

    @Steps
    TradeResponse theTradeDetails;
  
    @Then("the recorded trade should include the following details:")
    public void the_recorded_trade_should_contain_the_following_details(List<Map<String, String>> tradeDetails) {
        restAssuredThat(response -> response.statusCode(200));

        Map<String, String> expectedResponse = tradeDetails.get(0);
        Map<String, String> actualResponse = theTradeDetails.returned();

        assertThat(actualResponse).containsAllEntriesOf(expectedResponse);
    }

The TradeResponse class is responsible for retrieving the latest REST response and converting it to a map of strings.

public class TradeResponse {
    public Map<String, String> returned() {
       return mapOfStringsFrom(SerenityRest.lastResponse().getBody().as(Map.class));
    }

    private Map<String,String> mapOfStringsFrom(Map<String, Object> map) {
        return map.entrySet()
                .stream()
                .collect(toMap(Map.Entry::getKey,
                        entry -> entry.getValue().toString()));
    }
}

Living documentation

You can generate full Serenity reports by running mvn clean verify. This includes both the living documentation from the feature files:

And also details of the REST requests and responses that were executed during the test:

Want to learn more?

For more information about Serenity BDD, you can read the Serenity BDD Book, the official online Serenity documentation source. Other sources include:

serenity-rest-starter's People

Contributors

wakaleo avatar cliviu avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.