Victor Olex
Victor Olex

Reputation: 1498

How to avoid mutating objects from session scope fixtures in PyTest?

What is the best way to avoid side effects on objects returned from session-scoped fixtures?

My recipe is to wrap a session-scoped fixture with a function-scoped one, which returns a copy of the original object. Is there anything built-in in PyTest for that? Some decorator maybe?

import pytest
from pandas import read_csv

@pytest.fixture(scope='session')
def _input_df():
    """Computationally expensive fixture, so runs once per test session.
    As an example we read in a CSV file 5000 rows, 26 columns into a pandas.DataFrame
    """
    df = read_csv('large-file.csv')

    return df


@pytest.fixture(scope='function')
def input_df(_input_df):
    """"This is a function-scoped fixture, which wraps around the session-scoped one
    to make a copy of its result."""
    return _input_df.copy()


def test_df_1(input_df):
    """Inadvertently, this test mutates the object from the input_df fixture"""
    # adding a new column
    input_df['newcol'] = 0
    # we now have 27 columns
    assert input_df.shape == (5000, 27)


def test_df_2(input_df):
    """But since we are getting a copy or the original this test still passes"""
    assert input_df.shape == (5000, 26)

Upvotes: 3

Views: 1807

Answers (1)

SilentGuy
SilentGuy

Reputation: 2203

You should return the copy function object from input_df fixture without instantiating it:

import pytest
from pandas import read_csv

@pytest.fixture(scope='session')
def _input_df():
    """Computationally expensive fixture, so runs once per test session.
    As an example we read in a CSV file 5000 rows, 26 columns into a pandas.DataFrame
    """
    df = read_csv('large-file.csv')

    return df


@pytest.fixture(scope='function')
def input_df(_input_df):
    """"This is a function-scoped fixture, which wraps around the session-scoped one
    to make a copy of its result."""
    return _input_df.copy


def test_df_1(input_df):
    """Inadvertently, this test mutates the object from the input_df fixture"""
    # adding a new column
    input_df()['newcol'] = 0
    # we now have 27 columns
    assert input_df().shape == (5000, 27)


def test_df_2(input_df):
    """But since we are getting a copy or the original this test still passes"""
    assert input_df().shape == (5000, 26)

This code sample is working in my machine. test_df_2 test fails in this case.

Upvotes: 2

Related Questions