4 min read

When writing tests in pytest, you can use the standard Python assert statement to verify expectations and values. This assertion statement allows you to write concise tests and verify your code behavior. However, if you place an assertion statement in a helper function located in a different module, pytest will not be able to display the value that caused the assertion failure.

In this article, we will show you how to write and report assertions in tests using helper functions located in other modules than the testing modules in pytest. We will also demonstrate how to enable assertion introspection for those helper functions.

Writing Assertions in Test Functions

Let’s start with an example of how to write an assertion in a test function using the standard assert statement in pytest. Suppose we have a test function that checks if a value is equal to 4:

# content of test_dummy.py


def test_local_assert():
    a = 3
    assert a == 4

If this assertion fails, you will see the value of the variable a in the output:

$ pytest -vv -q -s
==================================== test session starts ====================================
platform linux -- Python 3.8.10, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: ~/register_assert_rewrite
collected 1 item                                                                            

tests/test_dummy.py::test_local_assert FAILED

========================================= FAILURES ==========================================
_____________________________________ test_local_assert _____________________________________

    def test_local_assert():
        a = 3
>       assert a == 4
E       assert 3 == 4

tests/test_dummy.py:6: AssertionError
================================== short test summary info ==================================
FAILED tests/test_dummy.py::test_local_assert - assert 3 == 4
===================================== 1 failed in 0.02s =====================================

This output provides valuable information about what went wrong and allows you to debug your code.

Writing Assertions in Helper Functions

Now let’s assume that we have a helper function that we want to use in multiple test functions. This helper function is located in a file called utils/helpers.py, and it checks if a result is empty after some processing:

# content of utils/helpers.py


def process_result(result: str) -> str:
    # dummy operation
    return f"even longer and {result} result"

def assert_result(result: str) -> None:
    # dummy check
    assert process_result(result) == ""

We can now import this helper function in our test file and use it in a test function:

# content of test_dummy.py

from utils.helpers import assert_result


def test_local_assert():
    a = 3
    assert a == 4


def test_imported_assert():
    a = "not empty"
    assert_result(a)

However, if the assertion fails, we will not see the value that caused the failure. Instead, we will only see the assertion error:

$ pytest -vv -q -s
==================================== test session starts ====================================
platform linux -- Python 3.8.10, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: ~/register_assert_rewrite
collected 2 items                                                                           

tests/test_dummy.py::test_local_assert FAILED
tests/test_dummy.py::test_imported_assert FAILED

========================================= FAILURES ==========================================
_____________________________________ test_local_assert _____________________________________

    def test_local_assert():
        a = 3
>       assert a == 4
E       assert 3 == 4

tests/test_dummy.py:8: AssertionError
___________________________________ test_imported_assert ____________________________________

    def test_imported_assert():
        a = "not empty"
>       assert_result(a)

tests/test_dummy.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

result = 'not empty'

    def assert_result(result: str) -> None:
        # dummy check
>       assert process_result(result) == ""
E       AssertionError

tests/utils/helpers.py:11: AssertionError
================================== short test summary info ==================================
FAILED tests/test_dummy.py::test_local_assert - assert 3 == 4
FAILED tests/test_dummy.py::test_imported_assert - AssertionError
===================================== 2 failed in 0.02s =====================================

To solve this issue, we can enable assertion introspection for the helper function.

Enabling Assertion Introspection for Helper Functions

To enable assertion introspection for a helper function located in a different module, we need to call the pytest.register_assert_rewrite() function before we import the module. This function tells pytest to rewrite the assertion statements in the specified module before running the tests.

We can add the following code to the conftest.py file in the root of the tests folder to enable assertion introspection for the utils/helpers.py module:

# content of conftest.py

# Pytest will rewrite assertions in test modules, but not elsewhere.
# This tells pytest to also rewrite assertions in utils/helpers.py.
# 
pytest.register_assert_rewrite("utils.helpers")

Now, if we run the test function again, we will see the value that caused the assertion failure:

$ pytest -vv -q -s
==================================== test session starts ====================================
platform linux -- Python 3.8.10, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: ~/register_assert_rewrite
collected 2 items                                                                           

tests/test_dummy.py::test_local_assert FAILED
tests/test_dummy.py::test_imported_assert FAILED

========================================= FAILURES ==========================================
_____________________________________ test_local_assert _____________________________________

    def test_local_assert():
        a = 3
>       assert a == 4
E       assert 3 == 4

tests/test_dummy.py:8: AssertionError
___________________________________ test_imported_assert ____________________________________

    def test_imported_assert():
        a = "not empty"
>       assert_result(a)

tests/test_dummy.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

result = 'not empty'

    def assert_result(result: str) -> None:
        # dummy check
>       assert process_result(result) == ""
E       AssertionError: assert 'even longer ... empty result' == ''
E         + even longer and not empty result

tests/utils/helpers.py:11: AssertionError
================================== short test summary info ==================================
FAILED tests/test_dummy.py::test_local_assert - assert 3 == 4
FAILED tests/test_dummy.py::test_imported_assert - AssertionError: assert 'even longer ... empty result' == ''
===================================== 2 failed in 0.02s =====================================

The output shows the expected value ('') and the actual value ('even longer and not empty result') that caused the assertion failure. This information can be very helpful when debugging the code.

Conclusion

Writing assertions in helper functions located in other modules can help you write more modular and reusable test code. However, if you do not enable assertion introspection for these helper functions, you will not see the actual value that caused the assertion failure.



If this article was useful to you, please share it using your favorite way for doing it! If you have suggestions of improvement or you’ve found something wrong, please let me know!


You are not being tracked on this website, no analytics, no cookies taken. I am still looking for a good plugin for comments which respects people's privacy. This website is hosted by Gitlab Pages which as far as stated by their general GitLab's privacy policy terms they collects potentially personally-identifying information like Internet Protocol (IP) addresses as mentioned in this thread.