Saturday, 20 April 2013

Cucumber-JVM BDD for RESTful API

In this post I will like to work out a example in Cucumber which takes on a very familiar setup these days in any kind of software organisation: Acceptance testing of a RESTful web service.

For anyone who has been following my posts, I tend to make use of online services in my testing examples. This time is no different and the goal is to:

Write set of acceptance tests for http://imdbapi.org's RESTful API

And I am going to use Cucumber-JVM achieve the objective. As the name suggest imdbapi.org exposes IMDB data via a RESTful api and we are going to search for movies! Try out following to understand the scope of this testing:

curl http://imdbapi.org/?title=Finding+Nemo
view raw curl cmd hosted with ❤ by GitHub


Setup Java Project with Maven

mvn archetype:generate -DgroupId=com.clearqa.test -DartifactId=imdb_rest
view raw gistfile1.txt hosted with ❤ by GitHub

Put all dependencies in pom.xml 
Since I have already coded up the test case my directory structure is looking like following:
├── pom.xml
└── src
├── main
│   └── java
└── test
├── java
│   └── com
│   └── clearqa
│   └── test
│   ├── ImdbSteps.java
│   ├── JsonReader.java
│   └── RunImdbTest.java
└── resources
└── com
└── clearqa
└── test
└── imdb_rest.feature


All or most of the fun in Cucumber is in writing the .feature file which is in Gherkin which is the language which many BDD tools like Cucumber understands.

Lets start looking my .feature file. It contains 2 Scenarios. First one is a simple scenario which represents 1 test case, while the second one is actually a set of test cases with different input and expected output. There are a lot of other ways which one can represent their set of acceptance test cases in Gherkin but for sake of example I have used the following and this file should be self explanatory which is what Gherkin is good at.

Feature: IMDB rest api gets
Scenario: Get movie by title
Given I query movie by "Finding Nemo"
When I make the rest call
Then response should contain:
"""
{
"title":"Finding Nemo",
"genres":["Animation","Adventure","Comedy","Family"],
"release_date":20030530
}
"""
Scenario Outline: Get many movies by title
Given I query movie by "<title>"
When I make the rest call
Then response should contain "<genres>"
Examples: No special characters in movie titles
| title | genres |
| Finding Nemo | {"genres":["Animation","Adventure","Comedy","Family"]} |
| Inception | {"genres": ["Action", "Adventure", "Mystery", "Sci-Fi", "Thriller"]} |
Examples: Special characters in movie titles
| title | genres |
| WALL·E | {"genres": ["Animation", "Adventure", "Family", "Romance", "Sci-Fi"]} |
| 8½ | {"genres": ["Mystery", "Sci-Fi", "Thriller"] } |


Now create a simple RunImdbTest.java file which will be used to run test cases in .features file via JUnit.

package com.clearqa.test;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@Cucumber.Options(format = {"pretty", "html:target/cucumber-html-report"})
public class RunImdbTest {
}


At this point you can run 'mvn test' and let Cucumber print out the snippet of code that you need to include in your Step Definition file where basically the conversion between plain english test statements happen to actual java code behind. In our case this file is ImdbSteps.java

ImdbStep.java

package com.clearqa.test;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.json.JSONException;
import org.json.JSONObject;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cucumber.api.java.en.*;
public class ImdbSteps {
final Logger logger = LoggerFactory.getLogger(ImdbSteps.class);
private String imdb_url;
private JSONObject json_response;
@Given("^I query movie by \"(.*)?\"$")
public void I_query_movie_by_title(String key) throws UnsupportedEncodingException {
imdb_url = "http://imdbapi.org/?title=" + URLEncoder.encode(key, "UTF-8") + "&type=json";
logger.info("http query = " + imdb_url);
}
@When("^I make the rest call$")
public void I_make_the_rest_call() throws IOException, JSONException {
json_response = JsonReader.readJsonFromUrl(imdb_url);
logger.info("Response = " + json_response.toString());
}
@Then("^response should contain:$")
public void response_should_contain_JSON(String expected_json_str) throws JSONException {
logger.info("Comparing reponse with" + expected_json_str);
JSONObject expected_json = new JSONObject(expected_json_str);
JSONAssert.assertEquals(expected_json, json_response, JSONCompareMode.LENIENT);
}
@Then("^response should contain \"(.+)\"")
public void response_should_contain(String expecgted_json_str) throws JSONException {
response_should_contain_JSON(expecgted_json_str);
}
}
view raw ImdbStep.java hosted with ❤ by GitHub

This Step Definition file contains a set of annotations with capture regular expressions that drive the whole automation between plain english statements and java code behind to do the job.

JsonReader.java

package com.clearqa.test;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class JsonReader {
private static String readAll(Reader rd) throws IOException {
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
return sb.toString();
}
public static JSONObject readJsonFromUrl(String imdb_url) throws IOException, JSONException {
URL url = new URL(imdb_url);
URLConnection uc = url.openConnection();
uc.addRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
uc.connect();
InputStream is = uc.getInputStream();
try {
BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
String jsonText = readAll(rd);
JSONArray json = new JSONArray(jsonText);
return (JSONObject) json.get(0); //There can be more than 1 movies
} finally {
is.close();
}
}
}
view raw JsonReader.java hosted with ❤ by GitHub


This helper class has to logic to http get the movie titles and convert the response in JSON objects for comparison in Step Definition class.

Finally our test cases are ready and we can run them using 'mvn test' command. This run will also create the green cucumber test report which all product managers like to see.



All project files are uploaded on github.

3 comments:

  1. I have one clarification..

    Thanks for sharing this example. but when I tried to use that example for one of my project, but its always working, even though if my REST API is in down mode also....please let me know why its not throwing any error message...

    ReplyDelete
  2. You can validate response code first, like if it's 200 then only read and parse the response json otherwise fail with appropriate message.

    ReplyDelete
  3. "Nice and good article.. it is very useful for me to learn and understand easily.. thanks for sharing your valuable information and time.. please keep updating.php jobs in hyderabad.
    "

    ReplyDelete