How to Do Python API Testing with Apiritif
January 18, 2021

How to Do Python API Testing with Apiritif

API Testing

Unit tests are a staple of functional software tests. The purpose of a unit test is to assert that a small-ish component, such as a single function or class, works as expected. While unit testing commonly occurs within a software project's codebase, you can also build upon these frameworks to test remote HTTP requests, such as API calls. Let's start with Python API testing.

Most programming languages support running such tests with unit test frameworks. This tutorial will demonstrate how to write unit tests for your API testing with Python and Apiritif. First, this tutorial will give an overview of Apiritif, a set of Python utilities for API testing that works with the nose unit test framework. We will start with a quick introduction to unit testing in general and unit testing in Python. Then, we write a first nose test and add an HTTP request. Finally, we discuss the assertions provided by Apiritif and how to design and build a practical API test.

Back to top

Can Python Be Used for API Testing?

Yes, Python is a popular unit testing framework for API testing. There are various Python modules you can use to run API unit tests, such as pytest and nose. Both will be discussed in this tutorial.

Back to top

How To Do Python API Testing

Structure of a Unit Test

The approach toward unit testing is the same in most programming languages and frameworks, including Python API testing. Every test case is a function or method, and there can be multiple test cases per file and multiple files of tests. Each of the test cases' implementation code calls the operation it wants to test (plus the necessary boilerplate and setup code) and contains one or more assertions regarding the result.

The setup code that needs to run for every test case is often the same, and there's sometimes also teardown code to run after every test case. These code blocks are called fixtures, and test frameworks allow writing this code once and executing it every time. However, we won't cover fixtures in this article.

When the test starts, the test framework runs through all test cases. It generates a report indicating which tests succeeded and which tests failed. Test frameworks differ in their organization of the tests, the report formats, and, most prominently, in the number, types, and syntax of assertions they provide.

visual of test frameworks and test files

Unit Testing in Python

Out of the box, Python already ships with the "unittest" module that provides a complete unit testing framework. While fully functional, it isn't the most developer-friendly option. Hence, third-party developers have contributed two alternative modules, nose and pytest. Both frameworks discover and collect test methods from your codebase, so you don't have to organize them into specific classes. That is just one example of increased simplicity. They also provide new assertion syntax and an ecosystem of third-party plugins.

Whether you prefer nose or pytest is mostly a matter of taste since both are pretty powerful, and their differences in basic functionality are minor. Nose is a direct extension of the unittest module, and pytest also has compatibility with unittest and nose tests. Hence, it's also generally possible to migrate from nose to pytest if needed, but vice versa is harder. In this article, we focus on nose since that's what Apiritif uses.

Writing Your First Nose Test

1. Create a new directory and a new file within that directory called test.py. Inside this file, add the following code:

deftestOnePlusOne():result=1+1assert(result==2

 

The first line defines the test method. Its name should start with "test". The second line is the operation you want to test. In this case, a simple addition. The last line contains the assertion.

2. To run the test, run the following command in the directory where you stored your test.py file:

nosetests

Your console should show that it ran one test and everything was OK. If the console cannot find the command, you need to install nose first, which you can do through pip:

pipinstallnose

 

3. If you want to see the output for a failing test, change the assertion to something like "assert (result == 3)" and rerun the test. It should report the failure.

Making HTTP Requests with Apiritif

4. To use Apiritif in your Python API testing cases, add the following line to the top of your test.py file:

fromapiritifimporthttp

 

Of course, you need to have the apiritif module installed before you can import it, so make sure to run the following installation command:

pipinstallapiritif

 

The HTTP object provides a similar interface as the Python requests module. If you've already used it to make HTTP calls, you should feel right at home. We'll use the Dog API for testing, a public, authentication-less API that provides open-source images of dogs. APIs like that are suitable for trying out new API testing tools. Once you've created some tests, you can adapt them for an API you've created or another third-party API you already use in your project.

5. Add the following test case to your test.py file:

deftestRandomDogImage():http.get("https://dog.ceo/api/breeds/image/random")

 

If you rerun the test, it should run successfully. But wait, isn't something missing? Right, there's no assertion yet! Let's add one, and this is where we get to the heart of Apiritif.

Adding Assertions

6. The Apiritif module provides several assertions for the response that HTTP requests return. Let's start with the most common expectation that the API returns the HTTP 200 OK status code:

deftestRandomDogImage():response=http.get("https://dog.ceo/api/breeds/image/random")response.assert_ok()

 

A test case that only asserts the HTTP status code is a smoke test, a first check on the API. When the test runs successfully, you know that the API replied, but you don't know if the response was as expected.

7. Let's add additional assertions to build out our test. For example, most APIs, including the Dog API, return JSON responses, which should have the proper HTTP header. With Apiritif, we can set these expectations like this:

response.assert_header_value('Content-Type','application/json')

 

8. Sometimes you cannot set expectations for a specific header value. For example, if you have an API that sets caching-related headers, you may want to assert that API responses have them, but you don't know the value since it may change depending on the status of the cache layer. In those cases, you can ask for just the presence of a header:

response.assert_has_header('X-Cache')

 

Checking Response Bodies

For a complete functional test of an API call, you should examine the response body, too. Apiritif has a set of assertion methods that you can use. Before we look at them, let's look at a sample response from the Dog API's random image endpoint that we've used before:

{"message":"https:\/\/images.dog.ceo\/breeds\/maltese\/n02085936_2020.jpg","status":"success"}

 

9. The response is a JSON object with message and status attributes. Since it's a random dog image, the message can be different each time, so we cannot set expectations for it, but we can check the overall format. A first basic check could be to assert the presence of a specific string somewhere in the response, such as "success":

response.assert_in_body('"success"')

 

10. Of course, a simple string check doesn't guarantee that we have the expected JSON structure. For that purpose, we can use JSONPath expressions and check for values at specific locations of the JSON document:

response.assert_jsonpath('status','success')

 

Since most APIs are in JSON, assert_jsonpath is probably your most used assertion method to validate expected API responses. However, Apiritif also checks XML/HTML responses using XPath expressions and even HTML bodies with CSS selectors. Hence, Apiritif isn't just for testing APIs, but you can check web pages, too. You can find a list of all supported assertions in the README file in the GitHub repository for Apiritif.

Negative Tests

Something that developers often overlook in software testing is negative testing. With a negative test, you don't assert that the API successfully handles a valid request. Instead, you affirm that the API handles errors gracefully and expectedly so that clients know how to deal with them.

10. You could check that non-existing endpoints return 404 errors, and invalid input causes a 400 error. For example, here's how you could test the 404 error:

deftestNonExistingEndpoint():response=http.get("https://dog.ceo/api/nonexistingendpoint")response.assert_status_code(404)

 

11. Every assertion method in Apiritif has a negation. You can always insert _not_ between assert and the rest of the method name to check that something doesn't apply for an API response, like this:

response.assert_not_in_body('"success"')

 

Two additional assertion methods that are useful for negative tests are those that cover all client errors (codes 400 to 499) and all server errors (500 to 599), respectively:

response.assert_4xx()response.assert_5xx()

 

By the way, these exist for all status codes. Hence, you can also use, for example, assert_2xx to check for a successful response even if you're unsure if it's a 200, a 201 (Created), or a 204 (No Content).

Back to top

Additional Features

This tutorial focused on the essential assertions that you can use to test API endpoints with your Python code and the nose unit testing framework. We will not cover additional features of Apiritif today, but we still wanted to mention them.

One of these features is transactions and smart transactions. Under normal circumstances, a test case in a unit test fails as soon as the first assertion fails. With transactions, you can group multiple requests and let them execute even if one of them fails.

The other feature is a CSV reader that facilitates reading test data from a file and using it as input for your test API requests.

You can find more documentation about these features in the README file.

Back to top

Start Testing

Functional API testing is crucial to ensure API quality. Apiritif is an excellent way to do API testing with Python. You can start writing test cases and running them locally today. If you want to test APIs in the cloud, check out BlazeMeter's functional API test functionality. It's also possible to integrate Apiritif with Taurus, one of the supported BlazeMeter test runners.

START TESTING NOW

Back to top