Link Search Menu Expand Document

Using fixtures in pytest

Imagine multiple tests use the same data. In this case, both tests use the same list.

def test_sum():
    assert sum([1, 2, 3, 3, 4, 5]) == 15

def test_length():
    assert len([1, 2, 3, 3, 4, 5]) == 5

This results in duplicate code. If you have 10 functions, even more. If you want to change the data, you must modify it everywhere.

You can create a data variable and use it in all tests. This solution is valid and may suffice in simple cases.

data = [1, 2, 3, 3, 4, 5]

def test_sum():
    assert sum(data) == 15

def test_length():
    assert len(data) == 5

However, pytest offers fixture for similar functionality with more control. The following code behaves the same, but you can pass the fixture to each test individually.

import pytest

@pytest.fixture
def data():
    return [1, 2, 3, 4, 5]

def test_sum(data):
    assert sum(data) == 15

def test_length(data):
    assert len(data) == 5

An interesting property is that pytest maintains the fixture value. If a test changes or deletes data, nothing happens. Each test accesses a separate copy, unlike the previous method.

@pytest.fixture
def data():
    return [1, 2, 3, 4, 5]

def test_sum(data):
    assert sum(data) == 15
    # We modify data here ...
    data.append(6)

def test_length(data):
    # ... but data does not change here
    assert len(data) == 5

Returning to our ISS example, we can use the fixture for the response as follows.

@pytest.fixture
def response():
    return {
        'iss_position': {'latitude': 0, 'longitude': 0},
        'message': 'success',
        'timestamp': 1596563200
    }

@pytest.fixture
def iss():
    return DistanceISS(0, 0)

def test_coordinates_iss_non_numerics(response, iss):
    with patch('iss.requests.get') as mock_get:
        mock_get.return_value.json.return_value = response
        mock_get.return_value.raise_for_status = lambda: None
        
        assert iss.above() == True

Finally, you can configure the scope of a fixture as follows.

@pytest.fixture(scope="session")
def data():
    return [1, 2, 3, 4, 5]

def test_sum(data):
    assert sum(data) == 15
    # We modify data here ...
    data.append(6)

def test_length(data):
    # ... and since it is a session fixture,
    # it is modified here as well
    assert len(data) == 5

The scope determines when it is recreated. By default, function is used, meaning each function receives a new fixture.

Other options include session, creating a single fixture for the entire session. If modified in a test, it affects the rest.