When testing code that should raise an exception, having multiple method calls in the tested code makes it unclear which call is expected to raise the exception.

Why is this an issue?

When writing tests for exception handling, clarity is essential. If you chain multiple method calls within the code being tested for an exception, it becomes ambiguous which specific call is expected to raise that exception.

For example, consider this code:

pytest.raises(IndexError, lambda: get_item().process())

Here, both get_item() and process() could potentially raise an IndexError. The test does not make it explicit which method is expected to raise the exception. This ambiguity makes the test harder to read and maintain.

By isolating the method call you want to test, you make your test explicit and unambiguous. This improves readability and maintainability, and makes your test suite a better specification of expected behavior.

What is the potential impact?

Writing tests with multiple method calls when testing for exceptions reduces test quality:

How to fix it in pytest

When using pytest’s pytest.raises() context manager, extract intermediate method calls into separate variables before the assertion. This ensures only the final method call that you want to test is inside the exception-testing block.

Code examples

Noncompliant code example

import pytest

def test_process():
    # Which method raises the exception: get_item() or process()?
    with pytest.raises(IndexError):  # Noncompliant
        get_item().process()

Compliant solution

import pytest

def test_process():
    item = get_item()
    with pytest.raises(IndexError):
        item.process()

How to fix it in unittest

When using unittest’s assertRaises() method, extract intermediate method calls before the assertion. This makes it clear which specific method is being tested for the exception.

Code examples

Noncompliant code example

import unittest

class TestMyCode(unittest.TestCase):
    def test_process(self):
        # Which method raises the exception: get_item() or process()?
        self.assertRaises(IndexError, lambda: get_item().process())  # Noncompliant

Compliant solution

import unittest

class TestMyCode(unittest.TestCase):
    def test_process(self):
        item = get_item()
        self.assertRaises(IndexError, item.process)

Resources

Documentation