
Rest-Assured with Cucumber: Using BDD for Web Services 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.
For example, here’s 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
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 would tie to backend code that actually executes the line. Of course, you can automate this from scratch, but there’s a really cool Java testing framework that has done all of the heavy lifting: Rest-Assured. This framework can be used as a standalone automation solution without Cucumber, but it also uses the Gherkin-style Given-When-Then structure so it lends itself quite nicely to being coupled with Cucumber.
Here are the step definitions that the scenario hooks into to enable the execution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
package cucumber.restassured; import static io.restassured.RestAssured.when; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; 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; public class BookStepDefinitions { private Response response; private ValidatableResponse json; private RequestSpecification request; private String ENDPOINT_GET_BOOK_BY_ISBN = "https://www.googleapis.com/books/v1/volumes"; @Given("a book exists with an isbn of (.*)") public void a_book_exists_with_isbn(String isbn){ request = given().param("q", "isbn:" + isbn); } @When("a user retrieves the book by isbn") public void a_user_retrieves_the_book_by_isbn(){ response = request.when().get(ENDPOINT_GET_BOOK_BY_ISBN); System.out.println("response: " + response.prettyPrint()); } @Then("the status code is (\\d+)") public void verify_status_code(int statusCode){ json = response.then().statusCode(statusCode); } /** * asserts on json fields with single values */ @And("response includes the following$") public void response_equals(Map<String,String> responseFields){ 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_contains_in_any_order(Map<String,String> responseFields){ 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())); } } } } |
As you can see, Cucumber and Rest-Assured are a match made in “web services automation heaven”! Both technologies are open source, so are free to download and use. And they both are pretty easy and straight forward to configure, so you’ll be up and running in no time.
Here’s the source code on GitHub.
Happy testRESTing!
toks
Nice article.. I have a quick question if you dont mind
If you need to reuse the step definition ‘verify_status_code’ (e.g. Status code is 200) in another step definition, how would you go about it? Reason why I am asking is because the ‘response’ object is set by the previous step definition (e.g userRetrieveTheBookByIsbn) and if you call the ‘verify_status_code’ step definition without the userRetrieveTheBookByIsbn step definition in another step definition class, I believe a null pointer exception will be returned.
Cucumber tends to support re use of step definition. In other words, if you want to verify the response status code for a service call in another step definition class and you type ‘the status code is’ within your feature file, I believe the already defined step (i.e. in BookStepDefinitions class) would be suggested. If you then re-use this step without using the previous step that actually sets the response object, exception will be returned.
How would you go about doing this?
Angie Jones
Hi Toks,
Yes, if you’re going to have multiple step definition files, you’ll need to use dependency injection (DI). There’s several options: PicoContainer, Spring, OpenEJB, etc. If you’re not already using DI, then I recommend PicoContainer. Otherwise, use the one that’s already in use, because you should only have one.
First thing you’ll need to do is add the dependency to your pom file:
Next, create a new class that holds the common data. For example:
Then, in each of your step definition files that you want to use this common data, you can add a constructor that takes StepData as an argument. This is where the injection occurs. For example:
Then you can use stepData to access all of the common fields needed across your step definition classes. For example, I can split the BookStepDefinitions class into two classes:
Then in another file:
This will allow you to share the state of the fields in StepData with all of your step definition files. More on DI for Cucumber is here:
https://cucumber.io/docs/reference/java-di
toks
Thanks for your swift reply and your work out here is greatly appreciated. I understand the concept of dependency injection that you’ve explained and I do use it as well. However, if for any reason you have the same step in 2 different feature files e.g. Feature A and Feature B.
The particular step is defined in the step definition for Feature A and its also available for re-use within Feature B. e.g.
Feature A
Scenario: Retrieve book with ISBN
Given ….
When …
Then the status code is 200
Feature B
Scenario: Add book to a catalogue
Given ….
When …
Then the status code is 200
My question is more like, if you have ‘verify_status_code’ in both BookStepDefinitions and SecondStepDefinitions (but ‘verify_status_code’ is implemented in BookStepDefinitions), Would you re-use the step defined in Feature A in Feature B or would you create another step (doing the same thing) with a different name in Feature B (DRY) ?
Also, I noticed that you moved ‘verify_status_code’ from BookStepDefinitions to SecondStepDefinitions, any reason for this?
Angie Jones
I would definitely reuse the step definition. But I wouldn’t leave it in BookStepDefinitions anymore because now it’s common. I would add a new file CommonStepDefinitions and move the verify_status_code to there so that it’s clear that this particular step is one that is applicable to more than one area. Any other steps that you come across that are common can be moved here as well.
No reason why I moved this method in my example other than to illustrate the global scope of the steps. 🙂
Kevin Mandeville
My belief when it comes to writing tests is to not go too far with the DRY principles. At times it makes sense to not repeat something many times like using constants for fields, etc, makes sense. But you can definitely take it way too far in trying to reuse things across tests. When it comes to writing tests I’m a firm believer in following the DAMP (descriptive and meaningful phrases) and not DRY. In writing tests, the most important piece is making sure you are writing thorough but most importantly, easy to ready, tests. If you take DRY principles too far, it becomes harder and harder to look at a single test and understand exactly what it’s testing, what the inputs are and what the outputs are. Think about it from the perspective of coming in to figure out why a test is failing. The more DRY it is, the longer it may take to diagnose where and why the test is failing. I’m completely ok with repeated things across tests if it means my individual tests themselves are easy to understand. Remember, you won’t be deploying your test code.
jagadeesh
Refactored my code… This article helped a lot..
Renzo
Hey Angie, thank you for the amazing explanation, it has helped me a lot in setting up my own framework. I did have a question though, and hopefully you can help me.
I have created a StepData class for all my common data to be shared among the stepdefs. However, I’m also using the @Before annotation of Cucumber for setting up my test which can currently be found in all my Stepdefinitions. As they are all the same, I was wondering whether it is ok to move the method to the StepData class?
Angie Jones
Renzo, so glad it’s been helpful 🙂 Yes, move the @Before to the common file. You don’t want to have it repeated in multiple files. As a rule of thumb, whenever you find yourself repeating yourself (duplicating code), it’s a sign to refactor and move the code to a central place where it can be shared across.
Vacha
Hey Angie,
Thanks for the wonderful post. Might not be exactly related to Rest assured, but can you give me some starting points on micro-services testing automation framework. I am getting few theoretical concepts but not anything concrete as in how to organize it with BDD-Cucumber like we just did here.
callel
http://www.googleapis.com/books/v1/volumes not working
Angie Jones
The parameter ‘q’ is required. This is being set on line 27 in the code above. The complete URL is
https://www.googleapis.com/books/v1/volumes?q=9781451648546
st
That was an excellent post and really helpful! I was wondering if you had a chance to implement Serenity with Cucumber-Rest Assured combination. The pico-container DI does not seem to work with Serenity and I am struggling to have the injection done.
Minoti Singh
Thanks a lot.
Very informative article.
can you explain me how do we run this project in Eclipse or using cmd prompt for Maven
Nils
I am very much thankful to you as this post has helped me a lot in automating web services.
I have a question on composite/nested service request.
I am able to get the response for non composite/ non nested service request by using
response = when().get(“ENDPOINT”);
But how do I validate composite/nested service request?
My requirement is I have endpoint say “http://localhost:8080/{company}/{employID}”, now when I hit this main-endpoint it internally makes another Get request to sub-endpoint say “http://localhost:8080/{company}” and get the response_1, depending on this response_1 status/content it will make Get call to the main-endpoint and get response_2.
Now I want to capture intermediate response_1 and perform some json validation and based on this proceed to main endpoint. Any suggestion is of more help and much appreciated. Thanks.
Jorge Viana
When you say “Given a book exists with an isbn of 9781451648546”, you already know this because you had to setup the database somehow prior to running the tests.
I guess your test should create the book with the desired isbn in the “Given…” step, this way you can freely change the isbn that you want to use in your tests.
Angie Jones
Thats one approach to test data management. However, in every application, customers/ testers do not have CREATE ability. This is a perfect example. I’m querying Google’s book API. There is no POST action available for this API.
Also, in the approach that you mention, there are drawbacks. While it allows greater flexibility, it also increases run time. In a case like this, that might cost an additional second or 2 but those few seconds can add up when you’re dealing with thousands of test. The cost becomes exponential when dynamically creating more complex test data that takes minute(s) to execute programmatically.
Jorge Viana
I see what you mean. Thank you for explanation. I always try to learn how to better test my APIs.
Raveendar Reddy
@Angie,
It’s a nice post.. but here I would like to see a framework with REST-Assured which is similar to the below framework..
https://github.com/intuit/karate
All I want to write my test cases in feature files. They are competing with REST-Assured.
nik
Business analysts write documentation and then developers implement the code in understandable language for business. i don’t understand why quality assurance engineer write it. I understand, just … ? strange phenomen.
Angie Jones
It’s actually the business analyst, developer, and tester (three amigos) who are supposed to collaborate to write the features.
nik
ccucumber not for quality assurance engineer . sorry. but you are also doing another things
Dev Prince
Awesome post!!!!!!!!!!!
Vijay
Hi,
Thanks it was easy to understand.
Query if there more than 1 item how to retrieve and if items has to be checked in jumbled manner.
Example: Key Value
items.volumeInfo.industryIdentifiers.type ISBN_13
Hemanth
Connection refused:Connect error message is coming for me
siva
Hi Angie Jones,
First of all thanks alot for great explanation.
can you explain me how do we run this project in Eclipse using Maven
siva
First of all thanks alot for great explanation.
can you add post request code also for this example.
Pavan
Hi ANGIE,
How do we deal with multiple Json Objects lets say i have json below
header
“id” : 12345
body
books[0]– data
books[1]– same structure with different data
i can use json path which is again static approach like i need to see the json and prepare everytime my json paths, but i wanted to handle it dynamically irrespective of json strucutre
any insights?
Tewari
can someone tell how to run this project please ?
Suresh
Please follow the Github location for sample code https://github.com/angiejones/restassured-with-cucumber-demo
G Anene
Hey Angie,Thank you for putting this together. I have used rest assured a few times in the past but what you have put down here has definitely given me a new insight to using it. Please can you spare me a few minutes to clarify an issue for me. For Post requests, what is the most efficient way to reuse request data? For example, If I run test “A” with a payload name “customerData.json”. If the data required for test “B” is slightly different from Test “A”, do I create a new json payload for test “B” or modify the data used in test “A” at runtime?.
Angie Jones
I would create a different payload for each test to keep it clean and minimize confusion.
Ankur Malik
Thanks for a great article.My question on this is:How do we debug it in Eclipse?I am not able to debug it and need help.And by debugging, I mean step into the Step file :)Thanks.
Angie Jones
Put the breakpoint in your StepDef glue code. So when that step is executed, it will go into the corresponding method and stop at your breakpoint.
Dmitrii
What if I need to check a null value?* and response contains|error|null|Will above steps work? Or we will get an assertion error because of NULL value != String null ?
Rajeev Kumar
HI ANGIE JONES,Thanks for such a nice tutorial in cucumber and rest assured.i was trying with same example google book libraray which you have mentioned earlier to validate below And response includes the following in any order | items.volumeInfo.title | Steve Jobs | | items.volumeInfo.publisher | Simon and Schuster | | items.volumeInfo.pageCount | 630 | |items[0].id | 8U2oAAAAQBAJ |error :java.lang.AssertionError: 1 expectation failed.JSON path items[0].id doesn’t match.Expected: iterable over [“8U2oAAAAQBAJ”] in any order Actual: 8U2oAAAAQBAJ
Rajeev Kumar
If you can help e to fix the problem, it would be great.
Angie Jones
instead of doing items[0]. id, you only need items.id
Rajeev Kumar
Now i am getting below error.
Rajeev Kumar
Pawan Singh
Discussions underneath the article are immense source of information for setting up this. Very Interactive loving it.. Thanks Angie. Can you share the git location for above framework..
Pawan Singh
I have seen many folks asking how to run this project. Since this is cucumber bases you need to create a runner class and use Junit runner. Below is the runner class.
Dilip agheda
How do you seperate out the test environment config files? e.g., my base URL is different depending on test,staging and prod. what’s the best way to have a config file that holds base URL and test data depending on environment. (my test data different based on environment too)
Marty Kane
Thanks a lot for this clear and concise example! It helped me get up and running super quickly testing my API with Cucumber. Great work.
Neha Shree
Above information is great! I am looking into reporting side of cucumber with rest-assured, do you have any recommendation on that? The basic report doesn’t provide request and response information. It would be very helpful if you can guide me in this.
Lorenzo
Hi Angie, thanks for the helpful post, really appreciate it.
Onkar
Hello Angie,Appreciate all your hard work, thank you for sharing such useful information, I have a question, the dependency injection using the constructor method – is it thread-safe, I wonder how does it work under concurrency as the common objects or data is shared across multiple steps. can you please explain this part? I hope I am able to convey my question to you, let me know otherwise. Many thanks in advance!