The goal is to make a working automated tests to cover GoRest's user creation which is triggered by calling the POST method that is described on their homepage. I will not be automating all the API endpoint's on GoRest's site like viewing users and updating their details. I purposefully left it out for the first timers as an exercise. But before we start I need you to sign up first so GoRest can give you an access token to authorize your request.  Getting an access token is very common in automating RESTFUL APIs and familiarizing yourself here is a good introduction.

An access token is a form of authentication mechanism that is given out to users to access an API. It can be a string that is usually put in request headers or request body. To know more about access token you can start by reading this wikipedia article

If you've been here accidentally through Google make sure you follow my previous posts as a prerequisite on this web series of tutorials on how to automate RESTFUL APIs.

To help you find the blog posts that I've mentioned and also as a quick review to those who are following the series, here's some quick links to refresh your knowledge.

  1. Setting up Python Behave Framework
  2. Understanding BDD, Feature Files and Step Implementation
  3. Quick Overview of HTTP Messages - (A Must Read!)
  4. Using Requests to make API Calls

And to glue the things that I've listed above let's get into action by writing our python code and by analyzing the HTTP request of user creation.

File Structure and GoRest's API Blueprint

We will be using these file structure that I've mentioned on Item 2 above, so you need to pattern your project folder similar to what you see below.

GoRest
├── features
│   ├── steps
│   │   ├── create_users.py
│   │   ├── query_users.py
│   │   ├── delete_users.py
│   │   ├── update_users.py
│   ├── test_create_users.feature
│   ├── test_query_users.feature
│   ├── test_delete_users.feature
│   ├── test_update_users.feature

Create User HTTP request

GoRest's public API is very simple but it lacks documentation on how to properly compose an HTTP  request to make a successful transaction. If you're a developer or if you've been used to seeing some REST API specs then you can easily guess on what are the needed requirements to create a request. So to make things easy for beginners. Here's the raw HTTP message for creating users

POST https://gorest.co.in/public-api/users
Content-Type: application/json
Authorization: Bearer YOUR-ACCESS-TOKEN
Accept: application/json

{
    "first_name": "John X",
    "last_name" : "Rocket",
	"gender": "male",
    "dob": "1962-08-12",
    "email": "johnrocket@yopmail.com",
    "phone": "+657765477",
    "website": "https://bit.ly/IqT6zt",
    "address": "Platform 3/4 end of rainbow street",
    "status": "active"
}

Now if you send an HTTP Message like this you can now create a user record which you can then view on their Data Management Tool.

Creating the Create-User Feature File

Here's a straightforward approach of creating a test scenario inside a feature file. There's a lot of things that can be improved which can be discussed in a separate post but for now let's settle on making things work first. Below is the test_create_users.feature

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
		

At a glance, the feature file seems to check a lot of things but all it does can be summarized into three things.

  1. Prepare the HTTP Request
  2. Execute the Request
  3. Check if the Response is returning the correct values.

Mapping The Feature Test Steps to Code

Now that we have the feature files ready we need to map the steps inside the scenario (Given, When, Then, And) to it's particular code implementation.

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" + "<YOUR_ACCESS_TOKEN>"}
    
@step('the user wants to create a record with first name as "John X"')
def set_first_name(context):
    context.request_body = {"first_name": "John X"}

The first line of the code is to import the behave library where we specify the step annotations that we will use. The given, when, then and step will map to the step scenarios in the feature file as a form of function annotation.  Take a closer look on how we use the given annotation on the following lines of code. As you can see we use the @ sign followed by the step keyword that we want it to map.

@given('A user with valid access token')
def set_access_token_in_header(context):
    context.header = {"Authorization": "Bearer " + "<YOUR_ACCESS_TOKEN>"}

The code snippet implements the Given part of the scenario and what it does is  assign the access-token in the context.header variable which is needed to successfully  execute the POST method.  One important thing to remember is that we need the context parameter as the first parameter when we define the function for the specific implementation.

The context parameter is important because it is where we store and access the shared data of the scenario in the feature file. If you've been coding before, you might be familiar to local and global scope and that data assigned in a variable that is initialized inside a function is just accessible inside that function. In order to access that data outside the scope of the function, Behave uses the context parameter.

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

the @step annotation is used for mapping the And keyword in the Feature File. if you use @and it will throw an error that such annotation does not exist. The code snippet is actually similar to the Given implementation as it prepares the necessary parameters of the request body.

The And step supplements the Given keyword and the Then keyword to form compounding ideas and statements. If you remember in my previous blog post the Given step is used to define preconditions. If there are multiple preconditions it is awkward to use multiple Given steps and it's natural to just use And.

Now that we know the essential elements when writing step implementations for our scenario let's finish the remainder of the code before we reach the When         part.

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

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

@step('date of birth is "1962-08-12"')
def set_date_of_birth(context):
    context.request_body['dob'] = "1962-08-12"

@step('an email of "johnrocketx@yopmail.com"')
def set_email(context):
    context.request_body['email'] = 'johnrocketx@yopmail.com'

@step('a phone number of "+637832233"')
def set_phone_number(context):
    context.request_body['phone'] = '+637832233'

@step('a website of "https://bit.ly/IqT6zt"')
def set_website(context):
    context.request_body['website'] = "https://bit.ly/IqT6zt"

@step('the address is "Platform 3/4 end of rainbow street"')
def set_address(context):
    context.request_body['address'] = "Platform 3/4 end of rainbow street"

@step('the user status is "active"')
def set_user_status(context):
    context.request_body['status'] = "active"

The code above are just string assignments to complete the context.request_body. And now that everything is in place we can now implement the call to POST method by writing the When step as you can see below

@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

The code above executes a POST HTTP call to GoRest's API and tells the server to create the user using the data we've prepared in the context.response_body and context.headers. The REST API will then respond to our request and we get the result on the response variable. After the HTTP call and getting the response, we do two things.

  • Get the contents of response body by invoking the .json() method and assign it to context.response_body.
  • Get the Status Code by accessing the field status_code from the response object and assigning it to context.status_code.

The two variables context.response_body and context.status_code will then be used to check that the REST API is returning the User based on the data that we've passed to them. This is where the remaining of the code will do, and that is to implement assertions.

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

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

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

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

@step('date of birth "1962-08-12" should be in response body')
def check_date_of_birth(context):
    assert context.response_body['result']['dob'] == "1962-08-12"

@step('email "johnrocketx@yopmail.com" should be in response body')
def check_email(context):
    assert context.response_body['result']['email'] == "johnrocketx@yopmail.com"

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

@step('website "https://bit.ly/IqT6zt" should be in response body')
def check_website(context):
    assert context.response_body['result']['website'] == "https://bit.ly/IqT6zt"

@step('address "Platform 3/4 end of rainbow street" should be in response body')
def check_address(context):
    assert context.response_body['result']['address'] == "Platform 3/4 end of rainbow street"

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

The above code is pretty straightforward and all it does is to check if the response body matches the data that we supplied in context.request_body and if the status code also matches on what we expect which is status 200

Now that everything is in place all we need to do is to run our tests and check out the result! You just need to activate your virtual environment and then go to your Project Directory and run behave in your terminal. It should start running your automated tests and output the results. If you are successful your terminal will output to something similar below.

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
21 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.362s

Next Steps

Now that you know how to implement your feature files to it's respective python code. Let's refactor our and parameterize our tests. Parameterization will make your tests more dynamic and reusable. After that we'll make use of Gherkin Tables and make our tests more Data Driven.