unittest
02 / 02

unittest: Mocking, Discovery & Best Practices

unittest: Mocking, Discovery & Best Practices

Mocking with unittest.mock

from unittest.mock import patch, Mock

# patch() temporarily replaces an object for the duration of the test,
# automatically restoring the original afterward -- avoids a real
# network call while still exercising the surrounding code
@patch('mymodule.requests.get')
def test_fetch_user(self, mock_get):
    mock_get.return_value.json.return_value = {'id': 1, 'name': 'Alice'}

    user = fetch_user(1)

    self.assertEqual(user['name'], 'Alice')
    mock_get.assert_called_once_with('https://api.example.com/users/1')

side_effect: Simulating Errors & Sequences

mock_api = Mock()

# Raise an exception when called -- tests how error paths are handled,
# which a static return_value can't simulate
mock_api.side_effect = ConnectionError('timeout')

with self.assertRaises(ConnectionError):
    mock_api()

# Or return different values on successive calls
mock_api.side_effect = [200, 500, 200]

MagicMock for Dunder Methods

from unittest.mock import MagicMock

# Plain Mock doesn't implement __len__/__iter__ etc. -- len(Mock())
# raises TypeError. MagicMock pre-configures the common dunder methods
# so code relying on Python protocols (len(), iteration, comparison)
# works against the mock without extra setup
mock_list = MagicMock()
mock_list.__len__.return_value = 3
self.assertEqual(len(mock_list), 3)

Running & Discovering Tests

# Recursively finds files matching test*.py and runs every TestCase found
python -m unittest discover

# Run a single test module, class, or method
python -m unittest test_math
python -m unittest test_math.TestMathUtils
python -m unittest test_math.TestMathUtils.test_addition

Best Practices

  • Keep tests isolated -- avoid shared mutable module-level state that lets one test's leftovers affect another, which produces order-dependent flaky failures.

  • Prefer setUp for per-test fixtures; reserve setUpClass for genuinely expensive, safely-shared setup like a DB connection.

  • An uncaught exception in a test is reported as an ERROR, distinct from a FAILURE (an assert* call that evaluated false) -- useful for diagnosing whether the test itself is broken vs. the code under test misbehaving.

  • Use @unittest.skip('reason') to document and temporarily disable a known-broken test rather than deleting or commenting it out.

  • pytest can run existing unittest TestCase classes unmodified, and offers plain assert statements with rich introspection plus a larger plugin/fixture ecosystem -- a common upgrade path once a project outgrows unittest's built-in feature set.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free