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
- Configuration of our project
- Creating our first test
- Refactoring our tests
- 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.
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
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
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
If everything is correct, your environment will be activated and on the cmd you will see like this:
(name_of_environment) C:\User\tests
To disable your environment just run:
deactivate
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
To activate our environment, we use the command:
source name_of_environment/bin/activate
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 %
To disable your environment just run:
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
pip install requests
And to make our tests, we will install the pytests
framework:
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 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
- 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
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 ==================================================================
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
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 =================================================================
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
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
After that, we need to change our test file. We will import this method that we created:
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
:
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
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
To generate the report, when running the tests, add:
pytest test.py --html-report=./report/report.html
Will be generated one file .html with the results of the tests, like this:
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)
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.
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.
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...
For mocking external API calls responses and HTTPretty are my favourite helpers
Thanks! This article was focused more on integration tests than unit tests, I'll take a look at your recommendations.
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
Nice! I'll take a look at this one, I've never heard before.
this should be
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
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...
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.
Omg, i'm a surprised with your article! Very nice job, congrats Alicia!
This article was incredible! Congrats on this series on showing how to test our code in different ways, you're awesome!
Thanks for sharing, cousin <3
Amazing how python is easy to work with and thanks for the didactics around the QA version of testing!
Great article, before reading this content I thought tests were something from another world, I'm looking forward to the next ones!
Perfect as always! Can't wait for the front-end test tutorials 🥰