DEV Community

Gaurav Singh
Gaurav Singh

Posted on • Originally published at automationhacks.io on

Python API test automation framework (Part 2) Making HTTP requests

Python and requests and header

Logos in header image sources:Python,Requests,JSON,HTTP

This is second post in a series on how to build an API framework using python.

Read part 1 here

Understanding API under test

Let’s understand the API that we will be using in this tutorial a bit better.

We will be using people-api which is a set of CRUD HTTP operations developed using Python Flask, SQLAlchemy and uses sqlite as the database and represents a list of persons with first name, last name and an id

To setup clone people-api repo from github and then activate the pipenv by running below:

pipenv shell

Enter fullscreen mode Exit fullscreen mode

Once done, Init the database by executing

python build_database.py

Enter fullscreen mode Exit fullscreen mode

This would seed the local sqlite database with some dummy records

Finally then start the HTTP service on your local by executing and ensuring this keeps on running during the test

python server.py

Enter fullscreen mode Exit fullscreen mode

We can see from the log that the local service has started, you would also see logs for any request made to the server

* Serving Flask app "config" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 105-008-664

Enter fullscreen mode Exit fullscreen mode

Let’s navigate to http://0.0.0.0:5000 to see the application,

Note: To see the swagger spec for this API we can navigate to http://0.0.0.0:5000/api/ui/#/

As you can see this API supports basic CRUD operations (CREATE, READ, UPDATE, DELETE)

Swagger preview

If you want to play around with these requests, I have also included a postman collection inside this repo under postman folder with all these basic operations already setup.

The API supports creating a person, updating him/her, deleting the person and able to read a user or all the users in the database. We will see these operations while writing tests.

First test, read via GET API

Let’s see how we can create a basic test to make this HTTP request via python using requestsmodule that we have already imported

The curl for this API is:

curl --location --request GET 'http://0.0.0.0:5000//api/people' \
--header 'Accept: application/json'

Enter fullscreen mode Exit fullscreen mode

Below is the code snippet that wires up a basic HTTP GET request and then asserts that the service returns us a 200 status code and that the returned list of people has kent in it.

Notice we use assert_that from assertpy module for fluent assertions, The failure messages from these are quite readable and we would discuss more about this in a future post. Please ensure you have that installed in your virtualenv using pipenv install assertpy

import requests
from assertpy.assertpy import assert_that
from config import BASE_URI

def test_read_all_has_kent():
    # We use requests.get() with url to make a get request
    response = requests.get(BASE_URI)
    # response from requests has many useful properties
    # we can assert on the response status code
    assert_that(response.status_code).is_equal_to(requests.codes.ok)
    # We can get python dict as response by using .json() method
    response_text = response.json()
    first_names = [people['fname'] for people in response_text]
    assert_that(first_names).contains('Kent')

Enter fullscreen mode Exit fullscreen mode

In above, we are using BASE_URI variable to get the base api url to be used. This is stored in aconfig.py file as below:

BASE_URI = 'http://0.0.0.0:5000/api/people'

Enter fullscreen mode Exit fullscreen mode

Creating a person using POST method

Alright, now that we have the basic case of GET API taken care of, let’s see how we can make a POST request.

Generally in Rest API’s when you need to create a resource you would use POST method

Below is the cURL to make the request, our python code is going to provide the same headers, body and URL to hit the request

curl --location --request POST 'http://0.0.0.0:5000//api/people' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-raw '{
   "fname": "TAU",
   "lname": "User"
 }'

Enter fullscreen mode Exit fullscreen mode

Here is the complete flow that we want to automate:

  • Create a new person in the db
  • Verify if the newly created user is present using the GET API
  • Filter to find if the created data is present in all the users in the system

Here Ln 2: calls create_new_person() where we first create the body to be passed as part of the request

Notice on Ln 14 we are using dumps() method from json module to convert python dict to json string (serialization) and using str(uuid4()) to get a unique last name on Ln 13 to ensure we don’t have conflicting data on every test run

Finally we make the POST request using

response = requests.post(url=BASE_URI, data=payload, headers=headers)

Enter fullscreen mode Exit fullscreen mode

And then use search_created_user_in() method to see if the newly created user is present in the database

Deleting a person using DELETE method

How would we go about the delete method?

In this API we can delete a person by passing in the person_id, it is always a good practice to create your own test data before trying delete operations instead of using any existing data since that could result in non-deterministic tests

So here it the flow:

  • Create a new person
  • Get the person id using read API
  • Hit the delete endpoint and assert

Since we already have methods to create and then read users, the only thing left is to implement the DELETE operation by ensuring the person_id is passed in the API path params, We use the fantasticf-strings to create our URL and then hit delete using requests.delete() method.

def test_created_person_can_be_deleted():
    persons_last_name = create_new_person()

    peoples = requests.get(BASE_URI).json()
    newly_created_user = search_created_user_in(peoples, persons_last_name)[0]

    delete_url = f'{BASE_URI}/{newly_created_user["person_id"]}'
    response = requests.delete(delete_url)
    assert_that(response.status_code).is_equal_to(requests.codes.ok)

Enter fullscreen mode Exit fullscreen mode

Bonus

Note in assertions we are not specifying numeric value for the status codes, requests provides a lookup dictionary wherein you can easily specify the code using readable english like syntax and this is definitely better than parsing what code X means onMDN - Mozilla developer network every time 😉

In other words instead of 200 we could write it as requests.codes.ok

assert_that(response.status_code).is_equal_to(requests.codes.ok)

Enter fullscreen mode Exit fullscreen mode

Conclusion

This is just a taste of what requests as a library can do. For more details on what all features it provides please read requests docs and I hope this gives you a good idea on how to use requests to make HTTP calls. In the next post we would discuss more on working with JSON/XML data types and assertions. Stay tuned. ⏰

And here are reads from MDN onContent-Type andAccept headers

If you found this post useful, Do share it with a friend or colleague and if you have thoughts, I’d be more than happy to chat over at twitter or comments. Until next time. Happy Testing and Coding.

Top comments (0)