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_additionBest 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