View on GitHub BDD For All

Flexible and easy to use library to enable your behavorial driven development (BDD) teams to easily collaborate while promoting automation, transparency and reporting.

Grammar (aka Step Definitions)

Step Definitions are code bocks associated with an expression that link to one or more Gherkin steps.

We wanted to create a simple lexicon with this library. Although there are many ways to describe a situation or step, the goal is to keep the these limited, which in turn makes them easier to use.



Cheatsheet | The Basics | Request Params | Request Payloads | Setting Headers | Handling the Response  



Id Step Applies To Description Params  
1 I am a (JSON| XML) API consumer Request Initializes a new JSON or XML request examples  
1a I’m a {string} Request Initializes a new JSON or XML request using a custom description (e.g. I’m a “Mobile device”. Useful when thinking more of consumers/devices. examples  
2 I am executing test {string} Request Set’s the test id and adds it as the “X-Correlation-ID” header (name of the header configurable - see configuration) examples  
3 I request (GET| POST\ PUT|PATCH|DELETE) {string} Request Set’s the request type and path for the request. examples
4 I request (GET| POST\ PUT|PATCH|DELETE) {string} on {string} Request Set’s the request type and path for the request, while overriding the default host for the request. examples
5 I set the (JSON| XML) body to Request Set’s the body as well as the content type based on the content provided. examples  
5a I set the (JSON| XML) body from values Request Set’s the body as well as the content type based on the content provided using a datatable of name value pairs and dot notations (e.g. users[0].name) examples  
5a I set the (JSON| XML) body from {file} Request Set’s the body as well as the content type based on the content provided using a file from disk examples  
6 I set the SOAPAction to {string} and body as Request Shortcut to supply a SOAPAction in addition to the payload. An alternative to calling the above step and adding a header step. examples  
7 I am submitting a form Request Ensures any params (or files) are packaged as form variables examples  
8a I provide the parameter {string} with a value of {string} Request Allows you to set individual parameters - an alternative to setting them as part of the path (e.g. /path?param=param) examples  
8b I provide the parameters Request Provide a list of parameters as a datatable - an alternative to setting them as part of the path (e.g. /path?param=param&param2=param2…) examples  
8c I provide the file {string} at {string} as {string } Response Submit a file (multipart) - it’s location is relative to where app is launched. (1) is param name, (2) is file location, & 3 is content type examples  
9 I provide the header {string} with a value of {string} Request Add a named header to the request in addition to the default headers you can add through configuration. examples  
10 I provide the headers Request Add a set of headers using a datatable examples  
11 I should get a status code of {int} Request Match the response code returned by the server examples  
12 record the response as {string} Request Save this request to the cache, so it’s headers and body can be referenced later. examples  
13 evaluating {string} should return (true| false) Response Evaluate a GPath expression against the response body (advanced use) examples  
14 the response value of {string} (should| should not) equal {string} Response Match a JSON/XML path value against a string or boolean value examples  
15 the response value of {string} (should| should not) equal (integer\ float|long) {string} Response Match a JSON/XML path against a numeric value examples
16 path {string} must occur (only| more than\ at least|less than|at most) {integer} times Response Match the number of occurances of a given path. examples
17 the value {string} must occur (only| more than\ at least|less than|at most) {integer} times for {string} Response Match the number of occurances of a path with a given string or boolean value. examples
18 the (integer| float\ long) {string} must occur (only|more than|at least|less than|at most) {integer} times for {string} Response Match the number of occurances of a path with given numeric value. examples
19 path {string} (does| does not) contain duplicates Response Check to see if a given path collection contains duplicates examples  
20 the response (should| should not) contain the following elements Response Check to see if the response has the elements list in the proceeding datable examples  
21 path value {string} should (equal| not equal) {string} value Respsonse Compare two response paths examples  
22 the following path values should (equal| not equal) each other Response Compare the paths in the proceeding datatable examples  
23 match header named {string} with a value of {string} Response Match a header value examples  
24 the following header name/value combinations are (equal| not equal) Response Match the proceeding header/value combinations examples  
25 I wait {long} (MILLISECONDS| SECONDS\ MINUTES) Request Sleep for a timeframe before executing the next step, could actually be any JAVA TimeUnit examples

Some things to remember…

  • Strings in parens - e.g. (should should not) represent the text options you can choose from. You can only choose one.
  • Strings - e.g. {string} - should be in quotes “I am a string”
  • True and false values are always represented as “TRUE” and “FALSE”
  • Header values are always string, no numeric or boolean comparisons
  • You can use variables in as any string/boolean/numeric value (see Chaining and Data Generation for more)

Examples

The following show examples of each of the step definitions. More can be found in the test directory in each of the .feature files.

The Basics  (back to top)

There are step definitions you will use almost all the time (if not all the time). These include first 4 steps in the above table as well as #11. These are all about request initialization and grabbing the initial response, see the below examples…

  Scenario: Status code
    Given I am a XML API consumer
      And I am executing test "XML_404"
     When I request GET "/404"
     Then I should get a status code of 404
     
  Scenario: Simple Post
    Given I am a JSON API consumer
      And I am executing test "SIMPLE_POST"
     When I request POST "/post"
     Then I should get a status code of 200

  Scenario: Domain override
    Given I am a JSON API consumer
      And I am executing test "DOMAIN_OVR"
     When I request POST "/" on "http://example.com"
     Then I should get a status code of 200     

In the first scenario, I am initializing a test as an XML consumer (e.g. I am a XML API consumer) as opposed to the second scenario which initializes as a JSON consumer (e.g. I am a JSON API consumer).

I then set up the test id for this scenario I am executing test "XML_404", which by default adds the “X-Correlation-ID” header to the request (although the name of this header is configurable.)

After that, I make a GET request for the path /404 (e.g. I request GET "/404"). Instead of a GET this could be a POST like it is in the second scenario (I request POST "/post"), but this tells our program the HTTP method and the path to hit.

Once everything is set up, I execute the request and check the status code with the last line I should get a status code of 200.

You’ll also notice in the last scenario we specify a host (e.g. I request POST "/" on "http://example.com"). You can configure a default host, but if for any reason you want to hit different domain, you can by using this step definition.

NOTE: Although not required, it’s highly recommended you set the test id. This will let you track and debug your applications much better.

   Scenario: Custom Consumer
     Given I'm a "Mobile device"
       And I am executing test "CUSTOM_CONSUMER"
      When I request GET "/json/users"
      Then I should get a status code of 200
       And the response value of "users[0].email" should equal "Sincere@april.biz"

The snippet above shows the scenario using a custom “initial context” or “given” (e.g. I’m a “Mobile Device”). In BDD it’s always good to relate stories to a particular user or context. This little feature let’s you alias the JSON or XML options so you can fit it better into your organization.

You must configure each of these contexts in your application.conf file. If there’s no mapping in the configuration file, will default to JSON parser. You can see a working example in our test configuration - /src/test/resources/application.conf - and test case - /src/test/resources/features/BasicSteps.feature in test BS4.

Setting up Request Parameters  (back to top)

There are a few ways to set up request parameters. It’s really around readability and what works best for the particular scenario.

  Scenario: Test sending parameters normally (SCENARIO 1)
  Given I am a JSON API consumer
   When I request GET "/params?param1=param1&param2=param2"
   Then I should get a status code of 200

   Scenario: Test sending parameters with a simple stepdef  (SCENARIO 2)
   Given I am a JSON API consumer
    When I request GET "/params"
     And I provide the parameter "param1" with a value of "param1"
     And I provide the parameter "param2" with a value of "param2"
    Then I should get a status code of 200
    
   Scenario: Test sending parameters as datatable  (SCENARIO 3)
   Given I am a JSON API consumer
    When I request GET "/params"
     And I provide the parameters
     | param1 | param1    |
     | param2 | param2    |
    Then I should get a status code of 200

As you can see, we’re sending the same request three different ways.

  • SCENARIO 1 - We’re simple sending it as part of the path. This is useful when there are one or two params with no encoding needed
  • SCENARIO 2 - We’re using the step definition to set each parameter, much like calling request.setParam(val, val). Will handle encoding of parameter names and values and is really good when there are one or two params.
  • SCENARIO 3 - When you have a lot of params and need the names/values encoded, this would be the best method.

Remember, it’s all about readability!

Setting up Form Parameters  (back to top)

To do a form submit, it’s a simple as adding I am submitting a form to your scenario. From there, you can use parameters, but instead of querystring, they’ll be moved to the body.

@Upload
Feature: Testing out uploads

  @Smoke
  Scenario: Simple form parameter example
    Given I am a JSON API consumer
    And I am executing test "US01"
    When I request POST "/formPost"
    And I am submitting a form
    And I provide the parameter "first" with a value of "mike"
    And I provide the parameter "last" with a value of "something"
    And I should get a status code of 200

  @Smoke
  Scenario: Simple upload example
    Given I am a JSON API consumer
    And I am executing test "US01"
    When I request POST "/uploadFile"
    And I am submitting a form
    And I provide the file "file" at "src/test/resources/data/file.txt" as "text/plain"
    And I should get a status code of 200

  Scenario: Upload file with form parameters
    Given I am a JSON API consumer
    And I am executing test "US01"
    When I request POST "/uploadFileX"
    And I am submitting a form
    And I provide the file "file" at "src/test/resources/data/file.txt" as "text/plain"
    And I provide the parameter "first" with a value of "mike"
    And I provide the parameter "last" with a value of "something"
    And I should get a status code of 200

You can use the same syntax from the params section (Scenarios 2 & 3) as well, a couple of important notes on forms, though…

  1. For this to work, don’t forget the I am submitting a form
  2. When submitting a file, the request will automatically switch to multipart
  3. File paths are relative. This is important when running from the command line

Request Payloads  (back to top)

There are times - a lot of them - when we need to send over a JSON or XML payload in the body of our request. The following shows how we’d accomplish this.

  Scenario: Testing a post
    Given I am a JSON API consumer
      And I am executing test "BSJ2"
     When I request POST "/json/users/post"
      And I set the JSON body to
      '''
      {
        "user": {
          "id": 1,
          "name": "Mike P",
          "complete": false
        }
      }
      '''
     Then I should get a status code of 201

  @Smoke @Json @ResponseMatch
  Scenario: Test setting up JSON as file (BSJ2)
    Given I am a JSON API consumer
      And I am executing test "BSJ2"
     When I request GET "/mirror"
      And I set the JSON body from file "src/test/resources/data/payload.json"
     Then I should get a status code of 200
      And the response value of "firstName" should equal "mike"
      And the response value of "nested.lastName" should equal "parcewski"  
     
  Scenario: Testing with SOAPAction
    Given I am a XML API consumer
      And I am executing test "BSJ2"
     When I request POST "/json/users/post"
      And I set the SOAPAction to "http://example.com/#GetDepartureBoard" and body as
      '''
      <element>
        <item>Hello!</item>
      </element>
      '''
     Then I should get a status code of 201   
     
  Scenario: Test setting up JSON as table (BSJ2)
    Given I am a JSON API consumer
      And I am executing test "BSJ2"
     When I request GET "/mirror"
      And I set the JSON body from values
      | users[0].id        | 1        |
      | users[0].name      | Mike     |
      | users[0].favorites | [1,2,3]  |
      | users[0].active    | true     |
      | users[1].id        | 2        |
      | users[1].name      | Bob      |
      | users[1].favorites | [4,7,9]  |
      | users[1].active    | false    |
     Then I should get a status code of 200
      And evaluating "users.count{ it.id == 1 } == 1" should return true
      And evaluating "users[0].id == 4" should return false

Nice and easy, right?

You can set the type, in this case it’s JSON, but you could set it to XML changing the step to I set the XML body to. This works with both POST as shown above and with GET (e.g. I request GET "/json/users/post") as well.

The second scenario shows a common shortcut we found ourselves using a lot. For soap requests (e.g. XML) it let’s you set the SOAPAction header and the body with one step definition.

For the third scenario, we’re using data tables. Using a simple “dot notation”, you can set up complex objects. In this case, the final JSON would look something like…

{
  "users": [
    {
      "favorites": [
        1,
        2,
        3
      ],
      "name": "Mike",
      "active": true,
      "id": 1
    },
    {
      "favorites": [
        4,
        7,
        9
      ],
      "name": "Bob",
      "active": false,
      "id": 2
    }
  ]
}

As you can see, we can have simple and complex objects, numbers and booleans all with this simple syntax. Some teams find this easier to use when working with the business.

Since we’re calling out features, you’ll notice that favorites which is surrounded in brackets (e.g. []). If you use brackets, any contents will be converted to an array with comma as a separator.

Another important note, this will add one of the following headers based on the type of payload…

  • JSON - ‘Content-Type: application/json; charset=utf-8’
  • XML - ‘Content-Type: application/xml; charset=utf-8’

Make note, it’s just not the content type that get’s set, but also the charset.

Notice the three ticks at the top and bottom of the payload, this let’s you write your JSON or XML unencumbered.

Setting up the Headers  (back to top)

Whether it’s set up content type, authorization, language type headers or more, we always find the need to add headers to our requests and this library gives you a few ways to do so.

 Scenario: Sending headers with stepdef
 Given I am a JSON API consumer
   And I am executing test "HS1"
  When I request GET "/headers"
   And I provide the header "header1" with a value of "header1"
   And I provide the header "header2" with a value of "header2"
  Then I should get a status code of 200

 Scenario: Sending headers as datatable
 Given I am a JSON API consumer
   And I am executing test "HS3"
  When I request GET "/headers"
   And I provide the headers
   | header1 | header1    |
   | header2 | header2    |
  Then I should get a status code of 200

The first option, much like the way we can set a parameter with a step def, set’s a single header. This is convenient for sending one or two headers over at a time.

And just like params, you have the option to specify headers in a data table as well (e.g. the second scenario).

But that’s not all!

You can also add default headers to every request by adding them to the configuration and as we mentioned in the basics section above, when the following stepdef is executed…

And I am executing test "HS3"

The value HS3 is added as a header with the name “X-Correlation-ID”, the name of which is also configurable.

When deciding on the method to set your headers, think about how they are used. Are they static (like user agent)? Or is it a basic auth which will change from run to run. This will help you decide if you should do through configuration or make it part of the scenario.

Handling the Response  (back to top)

We have a variety of ways of validating response data - pretty much rows #11 on in the cheatsheet. The best ways to see these in action is to check our test…

Also remember, to review request chaining and data generation as they allow you to create complex scenarios and workflows.

Don’t forget to read up on the GPath syntax, as you will be using it with everything you do.