Skip to content

Unit Tests

Unit tests verify a single function or class in isolation. They should use local, synthetic inputs and avoid external dependencies.

What to Unit Test

Unit tests are appropriate for:

  • Pure functions with deterministic outputs
  • Input validation and error handling
  • Edge cases and boundary values
  • Small local filesystem interactions using tmp_path

Unit tests are not appropriate for:

  • Cross-module workflows spanning many layers
  • Environment-dependent paths that need custom local setup
  • Network calls or long-running jobs

File Layout

Unit tests should mirror the source tree. Keep one test file per module when practical, using test_*.py naming.

Source module Test file
src/example/math_utils.py tests/unit/test_math_utils.py
src/example/config.py tests/unit/test_config.py
src/example/parser.py tests/unit/test_parser.py

Class Grouping

Group tests by the function or class they cover using a Test class. This keeps related tests together and makes failures easier to locate.

class TestParser:
    def test_rejects_empty_input(self): ...
    def test_accepts_valid_input(self): ...
    def test_normalizes_whitespace(self): ...

Synthetic Inputs

Create minimal, synthetic inputs instead of loading large fixtures.

def parse_int(value: str) -> int:
    return int(value.strip())

def test_parse_int_trims_whitespace() -> None:
    assert parse_int(" 42 ") == 42

Mocking Platform and Environment

Use unittest.mock.patch for external calls and monkeypatch for environment variables.

def test_env_var_overrides_default(monkeypatch):
    monkeypatch.setenv("APP_MODE", "test")
    assert load_mode() == "test"

Error Handling Tests

Test that functions raise the right exception with a useful message when given invalid input. Use pytest.raises with a match pattern to assert on the message.

def test_invalid_backend_raises(self):
    with pytest.raises(ValueError, match="invalid mode"):
        resolve_mode("bad-value")

Filesystem Tests

Use pytest's built-in tmp_path fixture for tests that need real files on disk. It creates a temporary directory that is cleaned up after the test.

def test_finds_single_pth(self, tmp_path: Path):
    (tmp_path / "config.toml").write_text("enabled = true\n")
    result = discover_config(tmp_path)
    assert result.name == "config.toml"

Tolerance in Floating Point Assertions

Floating point values should use tolerant comparisons.

assert abs(result - expected) < 1e-6