---
name: rendercv-testing-context
description: RenderCV test authoring standards, structure, and conventions. Use when writing or reviewing tests.
---

# RenderCV Testing Context

## References

- @tests/
- @docs/developer_guide/testing.md

## File structure

Each test file tests all classes and functions in its corresponding source file. The structure mirrors `src/rendercv/`:

```
src/rendercv/renderer/templater/date.py
    → tests/renderer/templater/test_date.py
    (tests all functions and classes in date.py)

src/rendercv/schema/models/cv/section.py
    → tests/schema/models/cv/test_section.py
    (tests all functions and classes in section.py)
```

## Naming conventions

Test names must include the name of the function or class being tested.

**When you need only one test**, use `test_` + the name:

- Testing `clean_url()` → `test_clean_url`
- Testing `Cv` → `test_cv`

**When you need multiple tests**, wrap them in a class using `Test` + PascalCase name:

- Testing `clean_url()` → `TestCleanUrl`
- Testing `Cv` → `TestCv`

Example with one test:

```python
@pytest.mark.parametrize(
    ("url", "expected_clean_url"),
    [
        ("https://example.com", "example.com"),
        ("https://example.com/", "example.com"),
        ("https://example.com/test", "example.com/test"),
    ],
)
def test_clean_url(url, expected_clean_url):
    assert clean_url(url) == expected_clean_url
```

Example with multiple tests:

```python
class TestComputeDateString:
    @pytest.mark.parametrize(...)
    def test_date_parameter_takes_precedence(self, ...):
        ...

    @pytest.mark.parametrize(...)
    def test_date_ranges(self, ...):
        ...

    @pytest.mark.parametrize(...)
    def test_returns_none_for_incomplete_data(self, ...):
        ...
```

## Use parametrize for variations

Instead of writing multiple similar tests, use `@pytest.mark.parametrize`:

```python
@pytest.mark.parametrize(
    ("input_a", "input_b", "expected"),
    [
        ("2020-01-01", "2021-01-01", "Jan 2020 – Jan 2021"),
        ("2020-01", "2021-02-01", "Jan 2020 – Feb 2021"),
        (2020, 2021, "2020 – 2021"),
    ],
)
def test_date_ranges(self, input_a, input_b, expected):
    result = compute_date_string(None, input_a, input_b, EnglishLocale())
    assert result == expected
```

## Shared fixtures with conftest.py

Place shared fixtures in `conftest.py`. Use the closest one possible:

- Fixtures for one folder → that folder's `conftest.py`
- Fixtures for multiple folders → their closest common parent's `conftest.py`

```
tests/
├── conftest.py                    # Used across all tests
├── schema/
│   ├── conftest.py                # Used by schema tests only
│   └── models/
│       └── cv/
│           ├── conftest.py        # Used by CV model tests only
│           ├── test_section.py
│           └── test_cv.py
└── renderer/
    └── ...
```

## Testing principles

**Keep tests focused.** Test functions in isolation: input → output.

**Don't create unnecessary fixtures.** If setup is one clear line, inline it:

```python
# Don't:
@pytest.fixture
def locale(self):
    return EnglishLocale()

def test_something(self, locale):
    result = format_date(Date(2020, 1, 1), locale)

# Do:
def test_something(self):
    result = format_date(Date(2020, 1, 1), EnglishLocale())
```

**Prefer real behavior over mocking.** Only mock when there's no practical alternative (external APIs, file system, etc.).

**Name tests by expected behavior, not by input:**

- Good: `test_returns_none_for_incomplete_data` - describes what should happen
- Bad: `test_function_with_none_input` - describes input but not behavior

**Keep tests simple:**

```python
def test_something(self, input, expected):
    result = function_under_test(input)
    assert result == expected
```

**What to test:**

- Input → expected output
- Input → expected error

**What to avoid:**

- Testing implementation details instead of behavior
- Complex test setup when simple values work
