Now that you know how to compose your Feature Files and write the implementation code of your API tests, let's now continue to improve on what we've started by using Scenario Outline to parameterize our tests. If you notice on what we wrote before, our scenario is pretty much static because you can only run the test on the specific data that we've provided. To make it more reusable we will convert our existing tests from Scenario to Scenario Outline and use the Example Table to lay out the data that we will use for different kinds of data inputs.

Converting from Scenario to Scenario Outline

To summarize, here are the things that we need to do to successfully convert our test scenario to a scenario outline

  • Identify the Test data that will most likely change in the Test Steps and replace it with a generic name that will describe the data itself and enclose it with < >.
  • Change the Scenario keyword to Scenario Outline and then add an Example Table at the bottom with the Test Data that we need to use for substitution.
  • Update the Steps Implementation by replacing the specific test data in the annotation with the generic name that we've used in the feature file.

Identify the Data that will most likely change and replace the Data with generic variables.

Feature: Create a user in go rest database
  Scenario: Create a User using valid data
    Given A user with valid access token
      And the user wants to create a record with first name as "John X"
      And the user record has a last name of "Rocket"
      And the gender is "male"
      And date of birth is "1962-08-12"
      And an email of "johnrocketx@yopmail.com"
      And a phone number of "+637832233"
      And a website of "https://bit.ly/IqT6zt"
      And the address is "Platform 3/4 end of rainbow street"
      And the user status is "active"
    When user submits the user data in "https://gorest.co.in/public-api/users"
      Then you should receive a "200" status code
      And first name "John X" should be in response body
      And last name "Rocket" should be in response body
      And gender "male" should be in response body
      And date of birth "1962-08-12" should be in response body
      And email "johnrocketx@yopmail.com" should be in response body
      And phone number "+637832233" should be in response body
      And website "https://bit.ly/IqT6zt" should be in response body
      And address "Platform 3/4 end of rainbow street" should be in response body
      And user status "active" should be in response body

From the feature file, you can see that the data inputs are already enclosed by double quotes and almost all of them will mostly change when you run the test with the exception of the endpoint url. The next thing that we need to do is to replace it with a generic variable enclosed with angle brackets <> that will describe the data that you want to substitute it with.  From the Feature File above it will then look like the one you see below

Feature: Create a user in go rest database
  Scenario: Create a User using valid data
    Given A user with valid access token
      And the user wants to create a record with first name as "<first_name>"
      And the user record has a last name of "<last_name>"
      And the gender is "<gender>"
      And date of birth is "<date_of_birth>"
      And an email of "<email>"
      And a phone number of "<phone_number>"
      And a website of "<website>"
      And the address is "<address>"
      And the user status is "<user_status>"
    When user submits the user data in "https://gorest.co.in/public-api/users"
      Then you should receive a "<status_code>" status code
      And first name "<first_name>" should be in response body
      And last name "<last_name>" should be in response body
      And gender "<gender>" should be in response body
      And date of birth "<date_of_birth>" should be in response body
      And email "<email>" should be in response body
      And phone number "<phone_number>" should be in response body
      And website "<website>" should be in response body
      And address "<address>" should be in response body
      And user status "<user_status>" should be in response body

Change the Scenario keyword to Scenario Outline and add an Example Table with Test Data

The variables that we add on the Scenario Outline will act as a placeholder to the Test Data that we will define in the Example Table. When we run the tests, each row on the Example Table will be used to run the test steps by replacing the variable in the test steps by the data defined in the column name. So if you have a total of 2 rows in the example table, the test Scenario will run twice. Here's how the Feature File will look after completing the Scenario Outline.

    Scenario Outline: Create a User using valid data
        Given A user with valid access token
        And the user wants to create a record with first name as "<first_name>"
        And the user record has a last name of "<last_name>"
        And the gender is "<gender>"
        And date of birth is "<date_of_birth>"
        And an email of "<email>"
        And a phone number of "<phone_number>"
        And a website of "<website>"
        And the address is "<address>"
        And the user status is "<user_status>"
        When user submits the user data in "https://gorest.co.in/public-api/users"
        Then you should receive a "<status_code>" status code
        And first name "<first_name>" should be in response body
        And last name "<last_name>" should be in response body
        And gender "<gender>" should be in response body
        And date of birth "<date_of_birth>" should be in response body
        And email "<email>" should be in response body
        And phone number "<phone_number>" should be in response body
        And website "<website>" should be in response body
        And address "<address>" should be in response body
        And user status "<user_status>" should be in response body

        Examples: Valid data
        | last_name | first_name | gender | date_of_birth | email                   | phone_number | website               | address                            | user_status |
        | Rocket    | John X     | male   | 1962-08-12    | johnrocketx@yopmail.com | +637832233   | https://bit.ly/IqT6zt | Platform 3/4 end of rainbow street | active      |
        | Assassin  | Space      | female | 1987-09-12    | space.x@yopmail.com     | 98832343     | www.youtube.com       | Black hole Avenue                  | inactive    |

Update The Steps Implementation by Replacing Specific Data with Variable Names used in the Feature File

Aside from updating the feature file we also need to replace the code implementation for it to work. Changing the code follows the same pattern on what we did to the Feature File. We just need to update the specific test data defined in the annotation and add another parameter to the function that corresponds to the generic variable used.

Here's a nice dissection of what happened when I updated the code implementation of the set_first_name() function so it will properly map the  step in the feature file.

@step('the user wants to create a record with first name as "{first_name}"')
def set_first_name(context, first_name):
    context.request_body = {"first_name": first_name}

Here are the things that I did

  • I updated the actual data with the variable name used in the feature file and enclosed it with curly braces
  • Added another variable as a parameter to the function similar to what I've used inside the annotation.
  • Use the parameter to compose the request body.

And to complete the remaining of the implementation you can check out the full source below.

from behave import given, when, then, step
import requests

@given('A user with valid access token')
def set_access_token_in_header(context):
    context.header = {"Authorization": "Bearer " + "JITQ3sA3SE4AJWtvzRpj-uG8cyiTdPrDRNmK"}
    
@step('the user wants to create a record with first name as "{first_name}"')
def set_first_name(context, first_name):
    context.request_body = {"first_name": first_name}

@step('the user record has a last name of "{last_name}"')
def set_last_name(context, last_name):
   context.request_body['last_name'] = last_name

@step('the gender is "{gender}"')
def set_last_name(context, gender):
   context.request_body['gender'] = gender

@step('date of birth is "{date_of_birth}"')
def set_date_of_birth(context, date_of_birth):
    context.request_body['dob'] = date_of_birth

@step('an email of "{email}"')
def set_email(context, email):
    context.request_body['email'] = email

@step('a phone number of "{phone_number}"')
def set_phone_number(context, phone_number):
    context.request_body['phone'] = phone_number

@step('a website of "{website}"')
def set_website(context, website):
    context.request_body['website'] = website

@step('the address is "{address}"')
def set_address(context, address):
    context.request_body['address'] = address

@step('the user status is "{user_status}"')
def set_user_status(context, user_status):
    context.request_body['status'] = user_status

@when('user submits the user data in "https://gorest.co.in/public-api/users"')
def execute_post_request_for_user_creation(context):
    response = requests.post('https://gorest.co.in/public-api/users', headers=context.header, json=context.request_body)
    context.response_body = response.json()
    context.status_code = response.status_code
    print(context.response_body)

@then('you should receive a "{status_code}" status code')
def check_status_code(context, status_code):
    assert context.status_code == int(status_code)

@step('first name "{first_name}" should be in response body')
def check_first_name(context, first_name):
    assert context.response_body['result']['first_name'] == first_name

@step('last name "{last_name}" should be in response body')
def check_last_name(context, last_name):
    assert context.response_body['result']['last_name'] == last_name

@step('gender "{gender}" should be in response body')
def check_gender(context, gender):
    assert context.response_body['result']['gender'] == gender

@step('date of birth "{date_of_birth}" should be in response body')
def check_date_of_birth(context, date_of_birth):
    assert context.response_body['result']['dob'] == date_of_birth

@step('email "{email}" should be in response body')
def check_email(context, email):
    assert context.response_body['result']['email'] == email

@step('phone number "{phone_number}" should be in response body')
def check_phone_number(context, phone_number):
    assert context.response_body['result']['phone'] == phone_number

@step('website "{website}" should be in response body')
def check_website(context, website):
    assert context.response_body['result']['website'] == website

@step('address "{address}" should be in response body')
def check_address(context, address):
    assert context.response_body['result']['address'] == address

@step('user status "{user_status}" should be in response body')
def check_user_status(context, user_status):
    assert context.response_body['result']['status'] == user_status

All you need to do now when you want to tests the user creation functionality with a new batch of data is to add another row at the bottom of the Example Table inside the feature file. There's no need to copy paste some code and add an additional block of Scenario in the feature file.

Next steps

Now that you know how to parameterize your tests let's delve into  environment controls by adding setup, teardowns and configuration file.