Python’s unittest module provides a test framework with test cases, assertions, test suites, mocking, and a test runner. import unittest. TestCase: class MyTest(unittest.TestCase) — each test_* method is a test; setUp() runs before each; tearDown() runs after. Assertions: assertEqual(a, b), assertNotEqual, assertTrue(x), assertFalse(x), assertIsNone(x), assertIsNotNone(x), assertIn(a, b), assertNotIn, assertRaises(Exc, fn, *args), assertAlmostEqual(a, b, places=7), assertRegex(text, pattern), assertDictEqual, assertListEqual, assertCountEqual (order-independent list equality). self.fail(msg) — unconditional failure. Assertions accept optional msg= for custom failure messages. Mocking: from unittest.mock import Mock, MagicMock, patch, call. Mock: m = Mock(); m(1, 2) → Mock; m.return_value = 42; m.side_effect = exc_or_list; m.assert_called_once_with(1, 2). patch: @patch("module.ClassName") or with patch("module.func") as mock_fn. subTest: with self.subTest(i=i): ... — loop over cases without full test failure on first bad case. Decorators: @unittest.skip("reason"), @unittest.skipIf(cond, "msg"), @unittest.expectedFailure. Running: python -m unittest discover -s tests -p "test_*.py". Claude Code generates test cases, fixture factories, mock patch helpers, assertion libraries, and CI test runners.
CLAUDE.md for unittest
## unittest Stack
- Stdlib: import unittest; from unittest.mock import Mock, patch, MagicMock
- Run: python -m unittest discover -s tests
- Assert: self.assertEqual(a, b) / assertRaises / assertAlmostEqual
- Mock: @patch("mymod.requests.get") / Mock(return_value=...)
- Sub: with self.subTest(n=n): self.assertEqual(fn(n), expected[n])
- Skip: @unittest.skipIf(sys.platform=="win32", "unix only")
unittest Test Suite Pipeline
# tests/testutil.py — base classes, factories, matchers, mock helpers, runner
from __future__ import annotations
import json
import os
import sys
import tempfile
import unittest
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Callable
from unittest.mock import MagicMock, Mock, call, patch
# ─────────────────────────────────────────────────────────────────────────────
# 1. Base test case with helpers
# ─────────────────────────────────────────────────────────────────────────────
class BaseTestCase(unittest.TestCase):
"""
Extended TestCase with convenience assertions and temp-file helpers.
"""
# ── Approximate and tolerance assertions ─────────────────────────────────
def assertApprox(self, actual: float, expected: float, rel: float = 1e-6) -> None:
"""Assert actual ≈ expected to relative tolerance rel."""
if expected == 0:
self.assertAlmostEqual(actual, 0, delta=rel)
else:
self.assertLess(abs(actual - expected) / abs(expected), rel,
msg=f"{actual} not ≈ {expected} (rel={rel})")
def assertBetween(self, value: Any, low: Any, high: Any) -> None:
"""Assert low <= value <= high."""
self.assertGreaterEqual(value, low, msg=f"{value} < {low}")
self.assertLessEqual(value, high, msg=f"{value} > {high}")
def assertSubset(self, subset: Any, superset: Any) -> None:
"""Assert every key/item in subset is in superset."""
for item in subset:
self.assertIn(item, superset, msg=f"{item!r} not in {superset!r}")
def assertDictSubset(self, subset: dict, full: dict) -> None:
"""Assert all key:value pairs from subset appear in full dict."""
for k, v in subset.items():
self.assertIn(k, full, msg=f"key {k!r} not in dict")
self.assertEqual(full[k], v, msg=f"dict[{k!r}]: {full.get(k)!r} != {v!r}")
def assertRaisesMessage(self, exc_type: type, message: str, fn: Callable, *args: Any, **kwargs: Any) -> None:
"""Assert fn raises exc_type and the exception message contains message."""
with self.assertRaises(exc_type) as ctx:
fn(*args, **kwargs)
self.assertIn(message, str(ctx.exception),
msg=f"Exception message {str(ctx.exception)!r} does not contain {message!r}")
def assertNoException(self, fn: Callable, *args: Any, **kwargs: Any) -> Any:
"""Assert fn does not raise; return the result."""
try:
return fn(*args, **kwargs)
except Exception as exc:
self.fail(f"Unexpected exception: {type(exc).__name__}: {exc}")
# ── Temporary file helpers ────────────────────────────────────────────────
def tmp_file(self, content: str = "", suffix: str = ".txt", encoding: str = "utf-8") -> Path:
"""
Create a temporary file with content; auto-deleted after the test.
Example:
path = self.tmp_file("hello\\nworld\\n", suffix=".log")
"""
tf = tempfile.NamedTemporaryFile(mode="w", suffix=suffix, encoding=encoding,
delete=False)
tf.write(content)
tf.close()
self.addCleanup(lambda: Path(tf.name).unlink(missing_ok=True))
return Path(tf.name)
def tmp_dir(self) -> Path:
"""Create a temporary directory; auto-deleted after the test."""
d = tempfile.mkdtemp()
import shutil
self.addCleanup(lambda: shutil.rmtree(d, ignore_errors=True))
return Path(d)
def tmp_json(self, obj: Any) -> Path:
"""Write obj as JSON to a temp file and return the path."""
return self.tmp_file(json.dumps(obj, indent=2), suffix=".json")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Parameterized test helpers
# ─────────────────────────────────────────────────────────────────────────────
def parametrize(cases: list[tuple]) -> Callable:
"""
Decorator for generating separate test methods from a list of (args...) tuples.
The test method receives the tuple values as positional arguments.
Example:
@parametrize([(2, 4), (3, 9), (4, 16)])
def test_square(self, n, expected):
self.assertEqual(n * n, expected)
"""
def decorator(fn: Callable) -> Callable:
def wrapper(self: unittest.TestCase) -> None:
for i, args in enumerate(cases):
with self.subTest(case=i, args=args):
if isinstance(args, tuple):
fn(self, *args)
else:
fn(self, args)
wrapper.__name__ = fn.__name__
return wrapper
return decorator
# ─────────────────────────────────────────────────────────────────────────────
# 3. Mock factory helpers
# ─────────────────────────────────────────────────────────────────────────────
def mock_response(
status_code: int = 200,
json_data: Any = None,
text: str = "",
headers: dict | None = None,
raise_exc: Exception | None = None,
) -> Mock:
"""
Build a Mock mimicking requests.Response.
Example:
mock_resp = mock_response(200, json_data={"id": 1, "name": "Alice"})
with patch("myapp.requests.get", return_value=mock_resp):
result = myapp.get_user(1)
"""
m = Mock()
m.status_code = status_code
m.text = text
m.headers = headers or {}
m.ok = 200 <= status_code < 300
if raise_exc:
m.json.side_effect = raise_exc
m.raise_for_status.side_effect = raise_exc
else:
m.json.return_value = json_data or {}
m.raise_for_status.return_value = None
return m
def mock_open_file(content: str, encoding: str = "utf-8") -> MagicMock:
"""
Return a MagicMock suitable for patching builtins.open with text content.
Example:
with patch("builtins.open", mock_open_file("key=value\\nfoo=bar")):
config = load_config("config.ini")
"""
from unittest.mock import mock_open
return mock_open(read_data=content)
def make_mock_db(rows: list[dict]) -> MagicMock:
"""
Build a MagicMock db cursor that returns rows from fetchall()/fetchone().
Example:
db = make_mock_db([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}])
db.cursor().fetchall() # [{"id": 1, ...}, {"id": 2, ...}]
"""
cursor = MagicMock()
cursor.fetchall.return_value = rows
cursor.fetchone.return_value = rows[0] if rows else None
cursor.rowcount = len(rows)
db = MagicMock()
db.cursor.return_value.__enter__ = Mock(return_value=cursor)
db.cursor.return_value.__exit__ = Mock(return_value=False)
db.cursor.return_value = cursor
return db
# ─────────────────────────────────────────────────────────────────────────────
# 4. Context helpers
# ─────────────────────────────────────────────────────────────────────────────
@contextmanager
def env_var(key: str, value: str):
"""
Context manager to temporarily set an environment variable.
Example:
with env_var("DATABASE_URL", "sqlite:///:memory:"):
app.init_db()
"""
old = os.environ.get(key)
os.environ[key] = value
try:
yield
finally:
if old is None:
del os.environ[key]
else:
os.environ[key] = old
@contextmanager
def capture_stdout():
"""
Capture stdout during a block; return the captured string.
Example:
with capture_stdout() as out:
print("hello")
assert out.getvalue() == "hello\\n"
"""
import io
buf = io.StringIO()
old = sys.stdout
sys.stdout = buf
try:
yield buf
finally:
sys.stdout = old
# ─────────────────────────────────────────────────────────────────────────────
# 5. Example test cases that demonstrate the utilities above
# ─────────────────────────────────────────────────────────────────────────────
class TestExamples(BaseTestCase):
"""
Demonstration tests — run with: python -m unittest testutil.TestExamples
"""
def setUp(self) -> None:
self.data = [1, 2, 3, 4, 5]
# ── Basic assertions ──────────────────────────────────────────────────────
def test_basic_math(self) -> None:
self.assertEqual(2 + 2, 4)
self.assertApprox(1 / 3, 0.3333333, rel=1e-5)
self.assertBetween(len(self.data), 1, 10)
def test_raises(self) -> None:
with self.assertRaises(ZeroDivisionError):
_ = 1 / 0
self.assertRaisesMessage(ValueError, "invalid literal",
int, "not_a_number")
# ── Parameterized ─────────────────────────────────────────────────────────
@parametrize([(0, 0), (1, 1), (2, 4), (3, 9), (5, 25)])
def test_square(self, n: int, expected: int) -> None:
self.assertEqual(n * n, expected)
# ── Temp files ────────────────────────────────────────────────────────────
def test_tmp_file(self) -> None:
path = self.tmp_file("hello\nworld\n", suffix=".txt")
self.assertTrue(path.exists())
self.assertEqual(path.read_text(), "hello\nworld\n")
def test_tmp_json(self) -> None:
data = {"name": "Alice", "scores": [10, 20, 30]}
path = self.tmp_json(data)
loaded = json.loads(path.read_text())
self.assertDictEqual(loaded, data)
# ── Mocking ───────────────────────────────────────────────────────────────
def test_mock_response(self) -> None:
resp = mock_response(200, json_data={"user": "Alice"})
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json(), {"user": "Alice"})
self.assertTrue(resp.ok)
def test_env_var(self) -> None:
original = os.environ.get("TEST_KEY", "<unset>")
with env_var("TEST_KEY", "testval"):
self.assertEqual(os.environ["TEST_KEY"], "testval")
# restored
self.assertEqual(os.environ.get("TEST_KEY", "<unset>"), original)
# ── Skip / expectedFailure ────────────────────────────────────────────────
@unittest.skip("demonstrating skip decorator")
def test_skipped(self) -> None:
self.fail("should not run")
@unittest.skipIf(sys.platform == "win32", "POSIX-only test")
def test_posix_only(self) -> None:
self.assertIn(os.sep, "/\\\\")
@unittest.expectedFailure
def test_known_broken(self) -> None:
self.assertEqual(0.1 + 0.2, 0.3) # float repr issue
# ─────────────────────────────────────────────────────────────────────────────
# Run
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
unittest.main(verbosity=2)
For the pytest alternative — pytest (PyPI) offers plain assert instead of self.assert* methods, fixture injection via function parameters, parametrize via @pytest.mark.parametrize, richer failure diffs, plugin ecosystem (coverage, xdist, benchmark), and conftest.py for shared fixtures; unittest is stdlib with zero dependencies and is compatible with pytest as a runner — use pytest for new projects both for its ergonomics and for the plugin ecosystem; use unittest when you cannot add test dependencies, when working in an environment that already uses unittest test runners (like Django’s manage.py test), or when you want stdlib-only code. For the hypothesis alternative — hypothesis (PyPI) performs property-based testing by automatically generating diverse test inputs from strategy specifications (@given(st.lists(st.integers()))), shrinking failures to minimal examples; unittest.subTest provides parameterized assertions over manually defined cases — use hypothesis for algebraic properties, parser round-trips, serialization correctness, and any function where you can state a property that must hold for all inputs; use unittest.subTest for explicit example-based tests where you control the exact inputs. The Claude Skills 360 bundle includes unittest skill sets covering BaseTestCase with assertApprox/assertBetween/assertDictSubset/assertRaisesMessage, tmp_file()/tmp_dir()/tmp_json() auto-cleanup helpers, parametrize() subTest decorator, mock_response()/mock_open_file()/make_mock_db() mock factories, env_var()/capture_stdout() context helpers, and complete TestExamples demonstrating all patterns. Start with the free tier to try Python unit testing patterns and unittest pipeline code generation.