Nov 24, 2020

Pytest Cassettes: Forget about mocks or live requests

What if I told you that you can simply record API communication on cassette and replay it anytime you want? Setup takes literally minutes, but imagine all the saved time on mocking all external communication? Priceless!

Writing integration and unit tests to cover the functionality of your API endpoints is useful, but having to send requests to all 3rd party APIs, which your application depends on, could be puzzling in the closed testing environment.

Pytest-recording library

To tackle this problem, I chose pytest-recording library, which is publicly available in Kiwicom’s Github repository. It takes features from and wraps it for simpler and more straight-forward usage.

The main idea is to record request-response pairs during the recording phase, save it to .yaml files, and reuse later. After recording and creating so-called cassettes, you use them as a replacement for real request-response communication.

Main benefits of pre-recorded cassettes:

  • Simulating HTTP (and other) communication including all the necessary headers, but also non-required ones.
  • It “patches” your code on the lowest level possible, thus providing you an interface to test also your error handling and other surrounding code in the functions.
  • Allows you to record authenticated requests, but ignore credentials while saving it and reusing recorded content for tests.

Getting started

We need to first install pytest-recording library either using pip or your favorite package manager inside your project or use our example code from branch plain-exampleinstall requirements and you can proceed to the fun part.
Our example provides a simple demonstration of a situation, where you can use our Locations API to obtain information about airports and stations with their countries. Then you input the airport/station code and our application can tell you, where it is located.

Preparing the config

Our default config for recording cassettes:

Default config for recording cassettes

Config defines that we won’t record any authorization header, which will exclude any credentials provided in the code. Also, we want to record only 3rd party APIs, so ignore_localhost option is set to ‘True’ value.

We use configuration file inside test/unit folder, where we placed this code, but you can write it directly to the file, where your respective tests are held. Also, configs have different scopes, from session to function, which is settable thru @pytest.fixture(scope="xxx") decorator. More about the configuration can be found in the docs.

Test and preparation

In the provided example, we have a simple test for the function find_country.

To enable recording, we will use @pytest.mark.vcr decorator for the function.


Recording and record modes are the tricky part of this tutorial. Description and their characteristics are described in the docs. Once we have config prepared, we start with "record_mode": "once" which will record the request-response pair and save it to cassettes/ folder. If you have everything prepared correctly, you can RUN your test and see whether your cassette file with the name test_main/test_find_country.yaml was created. If not, there is a troubleshooting section at the end of the article.

The next step after the successful recording is to set "record_mode": "none" inside the config to disable additional recording, so the .yaml file does not change without you noticing.

Profit and Summary

Now you can commit the cassettes folder into your git repository and during your test phase in CI/CD, you do not need to have an internet connection, credentials, or other information required to communicate with 3rd party APIs.

Bonus Tips and Tricks

One folder for all cassettes

The default setup for this library is to have one cassette for each function separately and one folder of cassettes per test file.

Common cassette folder for all the tests? Just override the default setting for cassettes folder using following code block inside

One cassette file to use across all the functions? Use parametrized decorator instead of default one.

Allow playback repeats

By default, each response in a cassette can only be matched and played back once while the cassette is in use. You can enable multiple playbacks of one cassette with parameter allow_playback_repeats in decorator.

Do you have any questions or tips regarding testing or Python engineering? ????
Feel free to reach me on 
LinkedIn or any other social network, where I usually go by the name Nordzisko. ????????
And don’t forget! “On the internet of things, nobody knows you are a fridge.” 

Join us

If developing software is your cup of tea, you might consider checking our open positions for Python Engineer or subscribe to newsletter to stay informed about upcoming events, new articles, and community activities.

Featured articles
Generating SwiftUI snapshot tests with Swift macros
Don’t Fix Bad Data, Do This Instead