DEV Community

Cover image for Automating your API tests using Python and Pytest
Alicia Marianne
Alicia Marianne

Posted on

Automating your API tests using Python and Pytest

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


Summary:


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.

Windows

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



 cd tests_with_python


Enter fullscreen mode Exit fullscreen mode

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:



 python -m venv name_of_environment


Enter fullscreen mode Exit fullscreen mode

The name of your environment can be anyone, just remember that python is case sensitive, take a look at PEP 8 Style Guide to learn more about Python convention.

To activate our environment, we use the command:



name_of_environment\Scripts\Activate


Enter fullscreen mode Exit fullscreen mode

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



(name_of_environment) C:\User\tests 


Enter fullscreen mode Exit fullscreen mode

To disable your environment just run:



deactivate


Enter fullscreen mode Exit fullscreen mode

Linux or MacOS

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



 cd tests_with_python


Enter fullscreen mode Exit fullscreen mode

To activate our environment, we use the command:



source name_of_environment/bin/activate


Enter fullscreen mode Exit fullscreen mode

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



(name_of_environment) your_user_name tests %


Enter fullscreen mode Exit fullscreen mode

To disable your environment just run:



deactivate


Enter fullscreen mode Exit fullscreen mode

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 requestslibrary to help us to make the requests:
PS: Make sure that your environment is activated before run this command



pip install requests


Enter fullscreen mode Exit fullscreen mode

And to make our tests, we will install the pytests framework:



pip install pytest


Enter fullscreen mode Exit fullscreen mode

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 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
The body response contains the list of asteroids
GET Search without any query parameter - Return a status code 403
GET Search with start date only - Return a status code 200
The body response contain the list of asteroid
GET Search with end date only - Return a status code 200
The body response contain the list of asteroid
GET Search in an valid range of dates - Return a status code 200
- The body response contain all fields non empty
GET Search when start date is bigger than end date - Return a status code 400
GET Search with invalid API Token - Return a status code 403
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) 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:



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


Enter fullscreen mode Exit fullscreen mode
  • 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:



pytest test.py


Enter fullscreen mode Exit fullscreen mode

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



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 ==================================================================


Enter fullscreen mode Exit fullscreen mode

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



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


Enter fullscreen mode Exit fullscreen mode

Now, running again our test:



================================================================ 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 =================================================================


Enter fullscreen mode Exit fullscreen mode

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:



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


Enter fullscreen mode Exit fullscreen mode

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:



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


Enter fullscreen mode Exit fullscreen mode

After that, we need to change our test file. We will import this method that we created:



from make_requests import make_request


Enter fullscreen mode Exit fullscreen mode

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:

Running our tests again:



============================= 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


Enter fullscreen mode Exit fullscreen mode

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:



pip install pytest-html


Enter fullscreen mode Exit fullscreen mode

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



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


Enter fullscreen mode Exit fullscreen mode

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

report_example

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.
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 💅🏼

Top comments (18)

Collapse
 
proteusiq profile image
Prayson Wilfred Daniel • Edited

Really good step by step introduction to testing with pytest.

I usually Mock external calls with Postman as I wish not to respect the service, which I don’t own, with my tests.

from os import environ
import requests

def make_request():

    base_url = environ.get("BASE_URL")
    params = {"api_key": environ.get("API_KEY"),}
    response = requests.get(url=base_url, params=params)

    return response

Enter fullscreen mode Exit fullscreen mode

Postman has a good section here on how to mock external API

We would then inject URL and keys from environment variables 😉 which enable us to change Mocked and Real in our CI/CD without changing our code.

So the actions that do unit tests, will have mocked values, and ones doing integration will have real.

Collapse
 
brentbrewington profile image
Brent Brewington • Edited

Really nice post! Always good to explain virtual environments while working with Python, and I like the straightforward pytest example you provide

Best practice with unit testing code that calls API's is to mock the API interface, so you don't have to ping an external service (idea of unit tests is to be able to run them very quickly with very little external dependencies, if any). Here's a great RealPython article that walks through how to do this mocking...and just a heads up this might get a little more advanced for some readers, but good to know it's out there!

realpython.com/testing-third-party...

^ heads up, that example uses "nosetest" library (extension of unittest) rather than "pytest"

Here's a great explanation of when it makes sense to actually call the external API in tests: pytest-with-eric.com/pytest-best-p...

Collapse
 
ldrscke profile image
Christian Ledermann

For mocking external API calls responses and HTTPretty are my favourite helpers

Collapse
 
m4rri4nne profile image
Alicia Marianne

Thanks! This article was focused more on integration tests than unit tests, I'll take a look at your recommendations.

Collapse
 
ldrscke profile image
Christian Ledermann

Have you heard of schemathesis, a tool that automates your API testing to catch crashes and spec violations? Built on top of the widely-used Hypothesis framework for property-based testing.

Also available as a service

Collapse
 
m4rri4nne profile image
Alicia Marianne

Nice! I'll take a look at this one, I've never heard before.

Collapse
 
ganeshpulsars profile image
Ganesh
pytest test.py --html-report=./report/report.html 
Enter fullscreen mode Exit fullscreen mode

this should be

pytest test.py --html=./report/report.html 
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ganeshpulsars profile image
Ganesh

Python 3.9.7
Platform Windows-10-10.0.19045-SP0
Packages pytest: 7.4.3
pluggy: 1.0.0
Plugins html: 4.1.1
metadata: 3.0.0

The above is my environment and the --html-report=./report.html did not work in this environment.

BTW, Sorry I forgot to mention that your tutorial was excellent. Even though I have been using python for past twenty years, I understood and started using pytest only after reading this. Thanks for the simple and lucid tutorial

Collapse
 
m4rri4nne profile image
Alicia Marianne

How I used pytest-html-reporter library, the recommendation on the documentation is to run:

--html-report=./report/report.html

You can check here more about it:
github.com/prashanth-sams/pytest-h...

Collapse
 
navcheru profile image
Naveen • Edited

Somehow, I am getting errors if my response includes dates. For the time being, I am using --html=./report/report.html. I need to find an alternative way to see a good HTML report that can show results in a graphical manner.

Collapse
 
suamirochadev profile image
Suami Rocha

Omg, i'm a surprised with your article! Very nice job, congrats Alicia!

Collapse
 
lliw profile image
William Rodrigues

This article was incredible! Congrats on this series on showing how to test our code in different ways, you're awesome!

Collapse
 
phenriquesousa profile image
Pedro Henrique

Thanks for sharing, cousin <3

Collapse
 
cherryramatis profile image
Cherry Ramatis

Amazing how python is easy to work with and thanks for the didactics around the QA version of testing!

Collapse
 
clintonrocha98 profile image
Clinton Rocha

Great article, before reading this content I thought tests were something from another world, I'm looking forward to the next ones!

Collapse
 
m1guelsb profile image
Miguel Barbosa

Perfect as always! Can't wait for the front-end test tutorials 🥰