How to Add Cucumber Layer on Top of REST-assured API Tests
This post provides a step-by-step guide on how to add a cucumber layer on top of api tests written in REST-assured.
REST-assured’s DSL already provides a BDD-style writing of tests in the Given-When-Then format, but it is still buried in the code. In other words, if you want to see what scenarios are covered, you still have to dig down into the api tests and read the code. There are no feature files.
The aim of this post is to refactor existing REST-assured api tests by adding cucumber and feature files, so that scenarios can be read more clearly without having to look at the underlying code.
REST-assured API Tests
In this example, we will write code to test user creation api.
First, we have a standalone REST-assured and JUnit Test, which resides in:
src/test/java/io.devqa/scenarios
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.junit.jupiter.api.*;
import static io.restassured.RestAssured.given;
public class UserTests {
private static String path;
private static String validRequest = "{\n" +
" \"username\": \"test-api-user\",\n" +
" \"email\": \"test-api-user@email.com\",\n" +
" \"password\": \"Passw0rd123!\",\n" +
" \"name\": \"Test Api-User\" \n}";
@BeforeAll
public static void setConfig() {
RestAssured.baseURI = "https://localhost:8080";
path = "/users";
}
@Test
public void shouldBeAbleToCreateNewUser() {
Response createUser = given()
.auth()
.preemptive()
.basic("MY_USERNAME", "MY_PASSWORD")
.header("Accept", ContentType.JSON.getAcceptHeader())
.contentType(ContentType.JSON)
.body(validRequest)
.post(path)
.then().extract().response();
Assertions.assertEquals(201, createUser.getStatusCode());
String username = createUser.jsonPath().get("username");
String email = createUser.jsonPath().get("email");
String name = createUser.jsonPath().get("name");
String id = createUser.jsonPath().get("id");
Assertions.assertEquals("test-api-user", username);
Assertions.assertEquals("test-api-user@email.com", email);
Assertions.assertEquals("Test Api-User", name);
Assertions.assertNotNull(id);
}
}
The above test can be run directly from the class as it can be invoked by JUnit.
The setConfig()
method sets the pre-requisite. The test method does the actions (sending the request) and then asserting on the response code and response payload.
Next, we will look at how to put the cucumber layer on top of the above REST-assured api test.
Cucumber and REST-assured API Tests
The first thing we need to do is to add the cucumber dependency in our project.
Using Gradle, in our build.gradle
file, we put these under the dependencies
:
dependencies {
testCompile "io.cucumber:cucumber-java:6.2.2"
testCompile "io.cucumber:cucumber-junit:6.2.2"
testCompile "io.rest-assured:rest-assured:3.3.0"
testCompile "com.jayway.jsonpath:json-path:2.4.0"
}
And these under configuration
in build.gradle
file:
configurations {
cucumberRuntime {
extendsFrom testImplementation
}
}
We also need to create a task in the build.gradle
file to run the cucumber feature files which contain the scenarios:
task cucumber() {
dependsOn assemble, compileTestJava
doLast {
mkdir 'build/test-results/'
javaexec {
main = "io.cucumber.core.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
args = ['--plugin', 'pretty', '--plugin', 'html:build/test-results/functional.html', '--plugin', 'junit:build/test-results/functional.xml','--tags', '@functional', '--glue', 'scenarios', 'src/test/resources']
}
}
}
Project Structure for Cucumber
We also need to modify our project structure to accommodate the changes for cucumber.
The feature files will be saved in:
src/test/resources/scenarios
The step definitions will be save in
src/test/java/scenarios
Next, we will create a feature file called UserScenarios.feature
and put it under src/test/resources/scenarios
folder.
The feature file will look like:
@functional
Feature: User Scenarios
Scenario: I should be able to create a new user
Given the users endpoint exists
When I send a valid create user payload
Then response status code should be 201
And create user response should be valid
Now we need to dismantle our REST-assured JUnit test to write step definitions that can be glued to the statements in our feature file.
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.junit.jupiter.api.Assertions;
import static io.restassured.RestAssured.given;
public class UserScenarios {
private String path;
private Response response;
private String validRequest = "{\n" +
" \"username\": \"test-api-user\",\n" +
" \"email\": \"test-api-user@email.com\",\n" +
" \"password\": \"Passw0rd123!\",\n" +
" \"name\": \"Test Api-User\" \n}";
@Given("the users endpoint exists")
public void preReq() {
RestAssured.baseURI = "https://localhost:8080";
path = "/users";
}
@When("I send a valid create user payload")
public void createUser() {
response = given()
.auth()
.preemptive()
.basic("MY_USERNAME", "MY_PASSWORD")
.header("Accept", ContentType.JSON.getAcceptHeader())
.contentType(ContentType.JSON)
.body(validRequest)
.post(path)
.then().extract().response();
}
@Then("response status code should be {int}")
public void checkResponseStatusCode(int code) {
Assertions.assertEquals(code, response.getStatusCode());
}
@And("create user response should be valid")
public void verifyResponse() {
String username = response.jsonPath().get("username");
String email = response.jsonPath().get("email");
String name = response.jsonPath().get("name");
String id = response.jsonPath().get("id");
Assertions.assertEquals("test-api-user", username);
Assertions.assertEquals("test-api-user@email.com", email);
Assertions.assertEquals("Test Api-User", name);
Assertions.assertNotNull(id);
}
}
As can be seen in the above step definitions, for every line in the scenario in the feature file, we have a corresponding step definition.
The method with the Given
annotation sets the pre-requisites. The method with the When
annotation does the action of sending the request and finally the method with the Then
annotation performs the assertions on the response.
To execute the above, all we need to do is to execute the command ./gradle cucumber
in a terminal from the project root.
Once the tests have run, the results are saved in build/test-results/functional.html
.
Conclusion
In this post, we covered a step-by-step guide on how to add a cucumber layer on top of the REST-assured API tests. By doing so, we can write our scenarios in feature files which become more readable by non-technical people.