Patrick's blog

Posted Mon 01 April 2019

Basic pytest fixture example

Pytest's fixtures are powerful. There are plenty of examples of how to use them in the docs.

However, I thought it might be beneficial to see how one might start to use fixtures and then how to build on top of that first step.

Problem

Before we do anything we need something to test. I chose the PokeAPI for this.

We want to test that when we fetch pokemon information by name from the API, we get data for that pokemon.

For example, if we ask the PokeAPI about "pikachu", we should get some data back with data.name == "pikachu" or something like that. Easy test.

Here's what it might look like:

### test_api.py ##########################

import requests

def test_pokeapi_get_pokemon():
    response = requests.get("https://pokeapi.co/api/v2/pokemon/ditto")
    assert 200 == response.status_code

    ditto_data = response.json()
    assert "ditto" == ditto_data["name"]

Fixtures

If we will be adding more tests in the future, we will probably be repeating the same base URL many times. We shouldn't repeat ourselves.

One way to avoid this is to use fixtures:

### conftest.py ##########################

@pytest.fixture
def base_url(request):
    return "https://pokeapi.co/api/v2"

### test_api.py ##########################

import requests

def test_pokeapi_get_pokemon(base_url):
    response = requests.get(base_url + "/pokemon/ditto")
    assert 200 == response.status_code

    ditto_data = response.json()
    assert "ditto" == ditto_data["name"]

Notice above in our test_pokeapi_get_pokemon function, base_url is the sole parameter for it.

This means that when we run our tests, our test function will have the base_url parameter provided to it by pytest using the return value of the base_url fixture.

This is not normal python behavior. This is a feature of pytest. It allows you to write tests in a way that are almost declarative. This is extremely convenient for the author of the tests but requires extra work to write them.

Advanced

Once comfortable with how fixtures work, we might want to consider using some advanced features to take our fixtures even further.

It might be useful to have a separate fixture for each route:

### conftest.py ##########################

@pytest.fixture
def base_url(request):
    return "https://pokeapi.co/api/v2"

@pytest.fixture
def pokemon(request, base_url):
    return base_url + "/pokemon"

### test_api.py ##########################

import requests

def test_pokeapi_get_pokemon(pokemon):
    response = requests.get(pokemon + "/ditto")
    assert 200 == response.status_code

    ditto_data = response.json()
    assert "ditto" == ditto_data["name"]

The most important thing to note here is this line in conftest.py:

def pokemon(request, base_url):

Notice that base_url is actually a reference to the fixture we'd already defined. So, the base_url in the pokemon fixture's signature will receive the return value of the base_url fixture.

This is a fixture that is using another fixture.

This is one of the most powerful aspects of fixtures in my opinion. It can make testing your application or library much easier and far less verbose than it currently is. Especially if you are repeating the same "setup" code in each of your tests.

Category: testing
Tags: python pytest fixtures