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.