Tips and Tricks - pytest

Monkeypatch Global Variables in Pytest (Permalink)

When writing code it is always a good idea to write testable code or easy to test code. However, sometimes you need to test code, which is not as easy to test as wanted. A useful trick that can assist you while testing code is monkeypatching global variables.

Suppose you have the following script.py:

from pathlib import Path

data_directory = Path(__file__).parent / "data"
print(data_directory)

The data_directory may be used for finding data resources or saving new files. However, when testing this code, you do not want to modify the documents in the data directory. Instead, a temporary directory should be used, where additional temporary files can be created. To do so, the data_directoryvariable needs to be overwritten in tests.

Fortunately, we can utilise pytest's monkeypatch fixture:

# test.py
import script


def test_overwrite_data_dir(tmp_path, monkeypatch) -> None:
    data_directory = tmp_path / "data"
    monkeypatch.setattr(script, "data_directory", data_directory)

    assert data_directory == script.data_directory

Test Not Raising an Exception (Permalink)

Pytest provides a context manager, which can be used to test that a certain piece of code raises an exception. In order to test different input values and whether or not an exception is raised, pytest's parametrisation feature can be combined with nullcontext from the contextlib module.

from contextlib import nullcontext as does_not_raise
import pytest


@pytest.mark.parametrize(
    "example_input,expectation",
    [
        (3, does_not_raise()),
        (2, does_not_raise()),
        (1, does_not_raise()),
        (0, pytest.raises(ZeroDivisionError)),
    ],
)
def test_division(example_input, expectation):
    """Test how much I know division."""
    with expectation:
        assert (6 / example_input) is not None

Reference: https://stackoverflow.com/a/68012715/6707020


Create Custom Pytest Markers (Permalink)

Pytest provides you with the capability to create custom markers in order to select or deselect tests more gradually. Suppose you want to mark all command-line interface-related tests with a custom cli marker. To do so, you need to register the marker first in your pytest.ini as follows:

[pytest]
markers =
    cli: mark a test as cli-related.

Now, you can use the marker cli to mark all command-line interface related tests with it as follows:

import pytest


@pytest.mark.cli
def test_some_cli_test():
    pass

Selecting marked tests is as easy as running:

$ pytest -m cli

... or deselecting them by using pytest's "not"-keyword:

$ pytest -m "not cli"

Reference: Marking test functions and selecting them for a run