Automating your API tests using Python and Pytest
Tags: python, testing, tutorial, automation
Published: 2023-11-12 18:50:49

Did you ever thought how you can tests your APIs using python? In the article we will learn how we can test our APIs using Python and the pytest framework.
For this tutorial you need to have the python installed, you can download it [here](https://www.python.org/downloads/) 

---
###  Summary:

- [What is Python and Pytest Framework](#what-is-python-and-pytest-framework)
- [Configuration of our project](#configuration-of-our-project)
	- [Creation of virtual environment with python](#creation-of-virtual-environment-with-python) 
	- [Setup of dependencies for the tests ](#setup-of-dependencies-for-the-tests) 
- [Creating our first test](#creating-our-first-test)
	- [Definition of the API that will be tested](#definition-of-the-api-that-will-be-tested)
	- [Creating our test](#creating-our-test)
- [Refactoring our tests](#refactoring-our-tests) 
	- [Generating html report result](#generating-html-report-result)
- [Conclusion](#conclusion)

---
## What is Python and Pytest Framework 

`Python` is a high-level, general-purpose programming language known for its simplicity and readability. It was created by Guido van Rossum and first released in 1991. Python is designed to be easy to learn and has a clean and concise syntax, which makes it a popular choice for both beginners and experienced programmers.

The `pytest` framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries.

---
## Configuration of our Project 

### Creation of virtual environment with python 

Before we start creating, let's understand what is an virtual environment on python.
A virtual environment in Python is a self-contained directory or folder that allows you to create and manage isolated Python environments for your projects. With environments you can easy manage your dependencies, avoid conflicts with different versions of python. 

A virtual environment is (amongst other things):

- Used to contain a specific Python interpreter and software libraries and binaries which are needed to support a project (library or application). These are by default isolated from software in other virtual environments and Python interpreters and libraries installed in the operating system.
- Contained in a directory, conventionally either named `venv` or `.venv` in the project directory, or under a container directory for lots of virtual environments, such as `~/.virtualenvs`.
- Not checked into source control systems such as Git.
- Considered as disposable – it should be simple to delete and recreate it from scratch. You don’t place any project code in the environment
- Not considered as movable or copyable – you just recreate the same environment in the target location.

You can read more about environments on python [here](https://docs.python.org/3/library/venv.html#venv-def).
#### Windows

First, create a folder for your project, after that, open your cmd and navigate to this folder using the command cd: 

```bash
 cd tests_with_python
```

If you don't know where your folder is, you can run the command `ls`, you will see the list of the folders and you can navigate through them. Inside of our project folder, run the follow command:

```bash
 python -m venv name_of_environment
```

The name of your environment can be anyone, just remember that python is case sensitive, take a look at [PEP 8  Style Guide](https://peps.python.org/pep-0008/) to learn more about Python convention.

To activate our environment, we use the command:

```bash
name_of_environment\Scripts\Activate
```

If everything is correct, your environment will be activated and on the cmd you will see like this:

```bash
(name_of_environment) C:\User\tests 
```

To disable your environment just run: 
```bash
deactivate
```

#### Linux or MacOS

Create a folder for your project, after that, open your cmd and navigate to this folder using the command cd: 

```bash
 cd tests_with_python
```

To activate our environment, we use the command:

```bash
source name_of_environment/bin/activate
```

If everything is correct, your environment will be activated and on the cmd you will see like this:

```bash
(name_of_environment) your_user_name tests %
```

To disable your environment just run: 

```bash
deactivate
```


### Setup of dependencies for the tests 

As we're going to test APIs, we need to install dependencies to help us during our tests, first we will install the `requests`library to help us to make the requests: 
PS: Make sure that your environment is activated before run this command
```bash
pip install requests
```

And to make our tests, we will install the `pytests` framework: 
```bash
pip install pytest
```

---
## Creating our first tests 

### Definition of the API that will be tested

For this tutorial, we'll use the Nasa API that return a list of asteroids: [Asteroids - NeoWs](https://api.nasa.gov/#donkiGST) and we will test the endpoint that Retrieve a list of Asteroids based on their closest approach date to Earth.

About the API:
- Base URL: `https://api.nasa.gov/neo/rest/v1/feed`
- Query parameters:

| Parameter | Type |Default |Description |
| ---------|---------|--------|-------------------|
|start_date|YYYY-MM-DD|none|Starting date for asteroid search|
|end_date|YYYY-MM-DD|7 days after start_date|Ending date for asteroid search|
|api_key|string|DEMO_KEY|api.nasa.gov key for expanded usage|


For this tutorial, we will focus on three types of tests:
- Contract: If the API is able to validate the query parameters that are sent 
- Status: If the status codes are correct 
- Authentication: Even this API doesn't requires the token, we can do tests with this 

Our Scenarios:

|  Method | Test | Expected Result |
| ---------|--------|-------------------|
| GET | Search with success | - Return a status code 200<br/> The body response contains the list of asteroids|
| GET | Search without any query parameter | - Return a status code 403<br/>|
| GET | Search with start date only|  - Return a status code 200 <br/> The body response contain the list of asteroid|
| GET | Search with end date only|  - Return a status code 200 <br/> The body response contain the list of asteroid|
| GET | Search in an valid range of dates| - Return a status code 200<br/> - The body response contain all fields non empty|
| GET | Search when start date is bigger than end date| - Return a status code 400 <br/>|
| GET | Search with invalid API Token| - Return a status code 403 <br/> The body response contain the list of asteroid|

### Creating our test

First, we will create a file called `tests.py` , at this file we will write our tests.  To help us to use good practices and write a good automated test, let's use [TDD(Test-Driven Development)](https://www.browserstack.com/guide/what-is-test-driven-development?psafe_param=1?keyword=&campaignid=&adgroupid=&adid=8784011037660164696&utm_source=google&utm_medium=cpc&utm_platform=paidads&utm_content=602353912717&utm_campaign=Search-DSA-NB-Europe&utm_campaigncode=Guide-Pages+1011769&utm_term=+&gad_source=1&gclid=CjwKCAiAxreqBhAxEiwAfGfndN8P705lwnkvEFnCz_lueR2hnhmZXgboBQEtKTaCIRbhcb1SXOxBYhoC-WoQAvD_BwE) technique. 

This technique consists in:
- RED - make a test that fail
- GREEN - make this test pass 
- Refactor - Refactoring what was done, removing duplication 

And to write a good suite of test, we will use the 3A technique:
- Arrange: prepare the context.
- Act: perform the action we want to demonstrate.
- Assert: show that the outcome we expected actually occurred.

Starting with red and using 3A technique, we will write the first test  `Search asteroids with success`:

```python
import pytest

def test_search_asteroids_with_sucess():
    # Arrange:
    api_key = "DEMO_KEY"
    #Act:
    response = make_request(api_key)
    #Assertion:
    assert response.status_code == 200  # Validation of status code  
	data = response.json()  
	# Assertion of body response content:  
	assert len(data) > 0  
	assert data["element_count"] > 0
```

- Arrange: We create an variable to insert the api_key, in this step, you can insert any data that will be necessary to execute your test. Normally, at this step we create mock data.
- Act: In this step we called the method responsible to make the request
- Assertion: We validate the response 

`The name of the method or class should starts with test`

To run our test, at command prompt, run:

```bash
pytest test.py
```

We will receive an error because we didn't created our method to do the request:

```bash
test.py F                                                                                                                                      [100%]

====================================================================== FAILURES ======================================================================
_________________________________________________________ test_search_asteroids_with_sucess __________________________________________________________

    def test_search_asteroids_with_sucess():
>       response = make_request()
E       NameError: name 'make_request' is not defined

test.py:5: NameError
============================================================== short test summary info ===============================================================
FAILED test.py::test_search_asteroids_with_sucess - NameError: name 'make_request' is not defined
================================================================= 1 failed in 0.01s ==================================================================
```

Now, let's create our method to do the request: 

```python
import requests

def make_request(api_key):
    base_url = "https://api.nasa.gov/neo/rest/v1/feed/"
    response = requests.get(f'{base_url}?api_key={api_key}')
	return response
```

Now, running again our test:

```bash
================================================================ test session starts =================================================================
platform darwin -- Python 3.11.5, pytest-7.4.3, pluggy-1.3.0
rootdir: /Users/Documents/tests_python
collected 1 item                                                                                                                                     

test.py .                                                                                                                                      [100%]

================================================================= 1 passed in 20.22s =================================================================
```

---
## Refactoring our tests 

Now that we already now how we create a test using pytest and how create a request, we can write the other tests and starting refactor the tests. The first refactor that we will do is to remove our request method from our test file. We will create a new file called `make_requests.py` that will contain our requests, and we will move the request that we did to this file:

```python
import requests

def make_request(api_key):
    base_url = "https://api.nasa.gov/neo/rest/v1/feed/"
    response = requests.get(f'{base_url}?api_key={api_key}')
	return response
```

Now, we need to think in an way to re-use this method for our other tests, cause we need to pass different parameters for different tests. There's a lot of ways that we can do it, for this tutorial, we will change the name of the parameter from `api_key` to `query_parameters`. We will do this to allow our method be more flexible and we can pass the parameters once for the tests: 

```python
import requests

def make_request(query_parameters):
    base_url = "https://api.nasa.gov/neo/rest/v1/feed/"
    response = requests.get(f'{base_url}?{query_parameters}')
	return response
```

After that, we need to change our test file. We will import this method that we created: 
```python
from make_requests import make_request
```

To have our tests organized in a better way, and following the recommendation of pytest documentation, we'll move our tests to a class `TestClass`:

{% gist https://gist.github.com/aliciamarianne1507/95a68871435ade84c4ab218efd6437fd %}

Running our tests again: 

```bash
============================= test session starts ==============================
collecting ... collected 7 items

test.py::TestClass::test_search_asteroids_with_sucess 
test.py::TestClass::test_search_asteroids_with_query_parameters_empty 
test.py::TestClass::test_search_asteroids_with_start_date 
test.py::TestClass::test_search_asteroids_with_end_date 
test.py::TestClass::test_search_asteroids_in_valid_range 
test.py::TestClass::test_search_asteroids_in_invalid_range 
test.py::TestClass::test_search_asteroids_in_invalid_token 

============================== 7 passed in 5.85s ===============================
PASSED             [ 14%]PASSED [ 28%]PASSED         [ 42%]PASSED           [ 57%]PASSED          [ 71%]PASSED        [ 85%]PASSED        [100%]
Process finished with exit code 0
```

### Generating html report result

To have a better visualization of your tests results, we can use the `pytest-html-reporter` library to generate an report html, to do it, first we need to install the package:

```bash
pip install pytest-html
```

To generate the report, when running the tests, add:

```bash
pytest test.py --html-report=./report/report.html 
```

Will be generated one file .html with the results of the tests, like this:

![report_example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eol3v7iex84t7prmyb9c.png)


## Conclusion 

This article is a tutorial of how you can start to write your automated tests for an API using python and pytest framework and how generate one report html. 
You can access the project used in this tutorial [here](https://github.com/aliciamarianne1507/tests_python). 
I hope this content will be useful for you. 

If you have any questions, feel free to reach out to me! 

Bisous, à la semaine prochaine 💅🏼