REST-Assured with Cucumber: Using BDD For API Automation

Behavior Driven Development (BDD) has become a popular approach in communicating requirements between stakeholders of agile teams. In fact, it’s so effective that it’s also being adopted in automation strategies by using Cucumber to write test scenarios in Gherkin (a non-technical, human readable language) and coupling them with an automation framework so that the scenarios are executable in the form that they are originally written.

While many teams use Cucumber to describe their UI testing, this open source software can be used for web service scenarios as well.

Let’s start by listing down what you need to know before you start and what building blocks you’ll need to complete this tutorial

Prerequisites

  • Java IDE – I use Eclipse
  • Setup a maven-based project
  • Setup your POM file appropriately for a basic project.  I’ll show you how to tweak it for the tutorial.

Building blocks

  • Understanding of BDD and Cucumber and how to write feature files
  • Basic Java coding skills
  • Web service fundamentals
  • Basic JUnit skills

For the purpose of this tutorial, let’s create a simple scenario that tests Google’s Books API to get a book by its ISBN.  This is written in a feature file using Cucumber.

Feature: Get book by ISBN
 Scenario: User calls web service to get a book by its ISBN
 Given a book exists with an isbn of 9781451648546
 When a user retrieves the book by isbn
 Then the status code is 200
 And response includes the following
 | totalItems | 1 |
 | kind | books#volumes | 
 And response includes the following in any order
 | items.volumeInfo.title | Steve Jobs |
 | items.volumeInfo.publisher | Simon and Schuster | 
 | items.volumeInfo.pageCount | 630 |

Each line of the scenario ties to back-end code that we want to test.  You can automate this from scratch, but you don’t need to thanks to a really cool Java testing framework that does all the heavy lifting:  REST-Assured.  This framework can be used as a standalone automation solution without Cucumber, but it also supports the Gherkin-style Given-When-Then structure so it lends itself quite nicely to being coupled with Cucumber.  That is what we will be doing in this tutorial.

OK, we have our feature file i.e. the scenario we want to verify.  The next step is to develop the glue code that we need to cause Cucumber to trigger the API test code.  This is typically known as a step definition file.  There is an easy way to auto-generate the step definition class.  Keep in mind, the majority of the code will be in Java.  Let me explain how to auto-gen the step def code.  You setup the project, build your maven POM file with the necessary deps, develop the feature file (as outlined in the example above) and a JUnit Test Runner Class.  Mine looks like this.

/**
* RunnerClass
*/
package com.telus.cucumber.bdd_training;

import org.junit.runner.RunWith;

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
@CucumberOptions(plugin = { "pretty", "json:target/cucumber-report.json",
"html:target/cucumber-htmlreport" }, features = { "src/test/features" })

public class RunnerClass {

}

The next part of the tutorial is to execute the RunnClass as JUnit. This will spit out the skeleton structures for our step definitions that we’ll use to develop the required Java code.  Here is what I got:

You can implement missing steps with the snippets below:


@Given("^a book exists with an isbn of (\\d+)$")
public void a_book_exists_with_an_isbn_of(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

@When("^a user retrieves the book by isbn$")
public void a_user_retrieves_the_book_by_isbn() throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

@Then("^the status code is (\\d+)$")
public void the_status_code_is(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

@Then("^response includes the following$")
public void response_includes_the_following(DataTable arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
// For automatic transformation, change DataTable to one of
// E,K,V must be a scalar (String, Integer, Date, enum etc)
throw new PendingException();
}

@Then("^response includes the following in any order$")
public void response_includes_the_following_in_any_order(DataTable arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
// For automatic transformation, change DataTable to one of
// E,K,V must be a scalar (String, Integer, Date, enum etc)
throw new PendingException();
}

 

We take the code that was spit out to the IDE console and past it into the target Java class that we are going to build that will contain the step definitions.  For this tutorial, we’ll create a class called BookStepsDefinitions.  Once you place those stubs in the Java class, you can re-run the RunnerClass as JUnit.  This time, you’ll get a different message printed to the IDE console.  Each method triggered will cause a PendingException.  That is to be expected as we haven’t written any code in the method stubs.  That is where all the fun is.  I won’t cover the basic parts of the Java code as that is outside of the scope of this tutorial.  It is very important that you have the REST-assured Javadocs open so you can figure out what you need code you need to write.  For this tutorial, I used the following 3.x Java Docs http://www.javadoc.io/doc/io.rest-assured/rest-assured/3.0.0.

 

package com.telus.cucumber.bdd_training;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.containsInAnyOrder;

import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;

public class BookStepDefinitions {

	private Response response;
	private ValidatableResponse json;
	private RequestSpecification request;

	// URL of REST API we are using for this tutorial.
	private String ENDPOINT_GET_BOOK_BY_ISBN = "https://www.googleapis.com/books/v1/volumes";

	@Given("^a book exists with an isbn of (\\d+)$")
	public void a_book_exists_with_an_isbn_of(String isbn) throws Throwable {
		// Write code here that turns the phrase above into concrete actions
		request = given().param("q", "isbn:" + isbn);
	}

	@When("^a user retrieves the book by isbn$")
	public void a_user_retrieves_the_book_by_isbn() throws Throwable {
		// Write code here that turns the phrase above into concrete actions
		response = request.when().get(ENDPOINT_GET_BOOK_BY_ISBN);
		System.out.println("response: " + response.prettyPrint());
	}

	@Then("^the status code is (\\d+)$")
	public void the_status_code_is(int statusCode) throws Throwable {
		// Write code here that turns the phrase above into concrete actions
		json = response.then().statusCode(statusCode);
	}

	/**
	 * asserts on json fields with single values
	 */
	@And("^response includes the following$")
	public void response_includes_the_following(Map<String, String> responseFields) throws Throwable {
		// Write code here that turns the phrase above into concrete actions
		// For automatic transformation, change DataTable to one of
		// List<YourType>, List<List<E>>, List<Map<K,V>> or Map<K,V>.
		// E,K,V must be a scalar (String, Integer, Date, enum etc)
		for (Map.Entry<String, String> field : responseFields.entrySet()) {
			if (StringUtils.isNumeric(field.getValue())) {
				json.body(field.getKey(), equalTo(Integer.parseInt(field.getValue())));
			} else {
				json.body(field.getKey(), equalTo(field.getValue()));
			}
		}
	}

	/**
	 * asserts on json arrays
	 */
	@And("^response includes the following in any order$")
	public void response_includes_the_following_in_any_order(Map<String, String> responseFields) throws Throwable {
		// Write code here that turns the phrase above into concrete actions
		// For automatic transformation, change DataTable to one of
		// List<YourType>, List<List<E>>, List<Map<K,V>> or Map<K,V>.
		// E,K,V must be a scalar (String, Integer, Date, enum etc)
		for (Map.Entry<String, String> field : responseFields.entrySet()) {
			if (StringUtils.isNumeric(field.getValue())) {
				json.body(field.getKey(), containsInAnyOrder(Integer.parseInt(field.getValue())));
			} else {
				json.body(field.getKey(), containsInAnyOrder(field.getValue()));
			}
		}
	}
}

The above Java code does the basics:

  1. Call the web service, request a book by ISBN
  2. Print the response object out to console – not required
  3. Check the HTTP response object, validate the HTTP status code == 200 OK.
  4. Check the HTTP response object body contains data provided in the feature file.

 

You can now re-execute the RunnerClass and this time you should be executing the REST-assured API code.  Assuming you got your code right on the first try, you should see something like this in your IDE.

capture3

In order to help with debugging, I highly recommend you use an HTTP debugging proxy to help you figure out how to interact with the service.  You can use the debugging proxy to help with exploratory testing.  Since I work mostly on Ubuntu, I use Charles proxy, but most Windows folks use Fiddler.

Here is an example of the Cucumber HTML reports we get, thanks to the setup in our POM file.

capture4

 

You must be logged in to post a comment.

Proudly powered by WordPress   Premium Style Theme by www.gopiplus.com
WordPress Appliance - Powered by TurnKey Linux