# How Are Tests Structured in the ai-job-search Repository?

> Explore the ai-job-search repository's test structure using Python's unittest. Learn how fixture-based setup and mocking ensure deterministic, offline execution.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: internals
- Published: 2026-09-02

---

**The ai-job-search repository uses Python's built-in `unittest` framework with all test modules living under a top-level `tests/` directory, employing fixture-based setup for temporary Git repositories and heavy mocking of external subprocess calls to ensure deterministic, offline execution.**

Understanding how are tests structured in the ai-job-search repository reveals a mature Python testing strategy that prioritizes isolation and reproducibility. The MadsLorentzen/ai-job-search codebase leverages the standard library's `unittest` module with custom fixtures to simulate Git workflows without touching live repositories. This architecture validates PDF verification utilities, upstream triage scripts, and skill specification linters in safe, temporary environments.

## Test Directory Layout and Naming Conventions

### File Organization

All test modules reside in the top-level `tests/` directory, which functions as a Python package via [`tests/__init__.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/__init__.py). Each core tool or feature receives its own dedicated test file to maintain granular coverage and navigability.

### Naming Patterns

Files follow the standard `test_*.py` naming convention required by `unittest` discovery. For example, PDF verification logic is tested in [`tests/test_verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_verify_pdf.py), while upstream synchronization logic lives in [`tests/test_upstream_triage.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_upstream_triage.py) and [`tests/test_check_upstream_updates.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_check_upstream_updates.py).

## Test Framework and Base Classes

### unittest.TestCase Subclasses

Every test class inherits from `unittest.TestCase`, utilizing built-in assertion methods like `assertEqual`, `assertTrue`, `assertIn`, and `assertRaisesRegex`. According to the ai-job-search source code, tests validate both happy-path execution and specific error conditions through explicit assertions on return codes, stdout, and stderr.

### Fixture Classes for Git Operations

The repository implements reusable fixture base classes such as `TriageRepoFixture` and `UpstreamCheckerRepoFixture` in files like [`tests/test_upstream_triage.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_upstream_triage.py). These fixtures encapsulate `setUp` and `tearDown` logic to create ephemeral Git repositories:

```python
class TriageRepoFixture(unittest.TestCase):
    def setUp(self):
        self.root = Path(tempfile.mkdtemp())
        self.addCleanup(shutil.rmtree, self.root, ignore_errors=True)
        (self.root / "tools").mkdir()
        shutil.copy(SCRIPT, self.root / "tools" / "upstream_triage.py")
        (self.root / ".github").mkdir()
        git(self.root, "init", "-b", "master")
        git(self.root, "config", "user.name", "Test")
        git(self.root, "config", "user.email", "test@example.com")
        git(self.root, "remote", "add", "upstream",
            f"https://github.com/{UPSTREAM_SLUG}.git")

```

## Isolation and Mocking Strategies

### Temporary Directory Isolation

To guarantee offline execution, most tests create isolated temporary directories using `tempfile.mkdtemp()`. The `setUp` method copies only the necessary scripts into these directories, ensuring the real repository remains untouched. The `addCleanup` handler with `shutil.rmtree` guarantees proper teardown.

### Mocking External Commands

The test suite heavily utilizes `unittest.mock.patch` to stub external system calls. This approach allows logic testing without requiring actual tool installations like Poppler PDF utilities:

```python
@patch("tools.verify_pdf.subprocess.run", side_effect=FileNotFoundError)
def test_reports_missing_poppler_command(self, _mock_run):
    with self.assertRaisesRegex(VerificationError, "pip install pypdf"):
        run_tool(["pdftotext", "example.pdf", "-"])

```

## CLI and Integration Testing

Integration tests invoke scripts via `subprocess.run([sys.executable, ...])` to verify CLI behavior end-to-end. The [`tests/test_check_upstream_updates.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_check_upstream_updates.py) file demonstrates this pattern by executing the checker script and asserting on output streams:

```python
result = self.run_checker("--remote", "upstream")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("up to date with upstream/master", result.stdout)

```

## Assertion Patterns and Coverage

The test suite mixes explicit assertions with regex-based error validation to cover both success and failure scenarios. Each major component maintains dedicated coverage:

- [`tests/test_verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_verify_pdf.py) covers PDF text extraction and verification utilities
- [`tests/test_upstream_triage.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_upstream_triage.py) validates upstream-fork synchronization logic
- [`tests/test_upskill_skill.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_upskill_skill.py) enforces skill specification compliance
- [`tests/test_check_upstream_updates.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_check_upstream_updates.py) tests remote fallback logic

## Running the Test Suite

Execute the complete suite using Python's standard discovery mechanism:

```bash
python -m unittest discover

```

This command automatically identifies all `test_*.py` files in the `tests/` directory and executes them against the standard library's test runner. The suite requires only optional dependencies like `yaml` and `pypdf` for specific functional tests, maintaining fast execution through comprehensive mocking.

## Summary

- **Directory structure**: All tests live in `tests/` with `test_*.py` naming for automatic discovery
- **Framework**: Built-in `unittest` with `TestCase` subclasses and custom fixture base classes
- **Isolation**: Temporary directories via `tempfile.mkdtemp()` ensure offline, repository-safe execution
- **Mocking**: Extensive use of `unittest.mock.patch` for external subprocess calls
- **Coverage**: Granular test files map to specific tools (PDF verification, upstream triage, skill linting)
- **Execution**: Standard `python -m unittest discover` with no custom test runners required

## Frequently Asked Questions

### What testing framework does ai-job-search use?

The repository uses Python's built-in `unittest` module exclusively. All test classes inherit from `unittest.TestCase` and utilize standard library assertion methods rather than third-party frameworks like pytest.

### How does ai-job-search isolate tests from the real repository?

Tests create temporary directories using `tempfile.mkdtemp()` in their `setUp` methods, then copy only required scripts into these isolated environments. Fixture classes like `TriageRepoFixture` manage this lifecycle, automatically cleaning up via `shutil.rmtree` in `tearDown` to prevent side effects.

### How are external dependencies mocked in ai-job-search tests?

The suite uses `unittest.mock.patch` to replace external commands such as `subprocess.run` and specific tool modules like `tools.verify_pdf.run_tool`. This allows testing error handling for missing dependencies (like Poppler PDF tools) without requiring actual installation.

### How do I run the tests in the ai-job-search repository?

Navigate to the repository root and execute `python -m unittest discover`. This discovers all `test_*.py` files in the `tests/` directory and runs them. The suite relies primarily on the standard library, though optional dependencies like `yaml` or `pypdf` may be needed for specific test modules.