---
title: "Case"
---

`Case` is a container for a single scenario: **inputs** you will pass to your System Under Test (SUT), plus optional **reference values** you’ll assert against.

Using `Case` enables:
- Iterating one merit function over many scenarios with `@merit.iter_cases(...)`
- Keeping inputs in `sut_input_values` (to call `sut(**case.sut_input_values)`)
- Storing reference data in `references` (typed or untyped)
- Tagging and filtering with `tags`, and attaching context with `metadata`

## Basic Usage

```python
import merit
from merit import Case


def classifier(text: str) -> str:
    return "positive" if "love" in text.lower() else "negative"


case1 = Case(
    sut_input_values={"text": "I love this"},
    references={"expected_label": "positive"},
    tags={"smoke"},
)

case2 = Case(
    sut_input_values={"text": "This is bad"},
    references={"expected_label": "negative"},
    tags={"regression"},
)


@merit.iter_cases(*[c for c in (case1, case2) if "smoke" in c.tags])
def merit_classifier(case: Case):
    label = classifier(**case.sut_input_values)
    assert label == case.references["expected_label"]
```

## Case API

### Adding input values

`sut_input_values` is a dictionary of keyword arguments that will be passed to your SUT:

```python
from merit import Case

case = Case(sut_input_values={"prompt": "Hello"})
```

### Typed references

Use `Case[YourModel]` to get IDE autocomplete and runtime validation of `references`.

```python
from pydantic import BaseModel
from merit import Case


class AgentReference(BaseModel):
    expected_keywords: list[str]
    min_response_length: int = 10


case = Case[AgentReference](
    sut_input_values={"prompt": "Say hello"},
    references=AgentReference(
        expected_keywords=["hello"],
        min_response_length=5
        ),
)

assert case.references.min_response_length == 5
```

### Untyped references

The default `Case` uses untyped references (a dict) and `references` defaults to `{}`:

```python
from merit import Case

case = Case(
    sut_input_values={"x": 2},
    references={"expected": 4},
)

assert case.references["expected"] == 4
```

### Providing ID for persistence

Each case has an `id: UUID` (auto-generated by default). Provide it explicitly when you want stable case IDs across runs or when you store cases in datasets.

This matters because `@merit.iter_cases(...)` uses `case.id` to build readable, stable parametrization IDs.

```python
from uuid import UUID

from merit import Case


case = Case(
    id=UUID("00000000-0000-0000-0000-000000000001"),
    sut_input_values={"x": 1},
)
```

### Adding metadata

Use `metadata` for extra context and reporting. Values must be JSON-like primitives (`str | int | float | bool | None`).

```python
from merit import Case

case = Case(
    sut_input_values={"prompt": "Hello"},
    metadata={"priority": "high", "latency_budget_ms": 200},
)
```

### Adding tags for filtering

Use `tags` to label cases and then select subsets with normal Python:

```python
from merit import Case

cases = [
    Case(sut_input_values={"x": 1}, tags={"smoke"}),
    Case(sut_input_values={"x": 2}, tags={"regression"}),
]

smoke_cases = [c for c in cases if "smoke" in c.tags]
```

### Validating cases for a SUT

If cases come from files/APIs, validate them against your SUT’s signature before running:

```python
import merit
from merit import Case, validate_cases_for_sut


def my_agent(prompt: str, temperature: float = 0.7) -> str:
    return f"{prompt} @ {temperature}"


cases = [
    Case(sut_input_values={"prompt": "Hello", "temperature": 0.5}),
    Case(sut_input_values={"prompt": "Hi"}),
    Case(sut_input_values={"invalid": "param"}),
]

# Fail fast (default)
# validate_cases_for_sut(cases, my_agent)

# Or filter invalid cases
valid_cases = validate_cases_for_sut(cases, my_agent, raise_on_invalid=False)


@merit.iter_cases(*valid_cases)
def merit_my_agent(case: Case):
    my_agent(**case.sut_input_values)
```

## Recommendations

### 1. Use Cases when data comes from external sources

If you're hardcoding inputs directly in your merit functions, you probably don't need `Case`.

**Don't do this:**
```python
from merit import Case, iter_cases

# Hardcoding simple data in Case objects is unnecessary
cases = [Case(sut_input_values={"x": 1}), Case(sut_input_values={"x": 2})]

@iter_cases(*cases)
def merit_simple(case, add_one):
    result = add_one(**case.sut_input_values)
    assert result
```

**Do this:**
```python
import merit

# For simple hardcoded data, use parametrize directly
@merit.parametrize("x", [1, 2, 3, 4, 5])
def merit_simple(x, add_one):
    result = add_one(x)
    assert result
```

When loading cases from JSON/APIs, prefer `Case[YourModel]` (typed `references`) and validate inputs up-front with `validate_cases_for_sut(...)`.

### 2. Use tags and metadata for dynamic case selection

Structure your cases with tags and metadata to enable flexible selection without changing merit code.

```python
import merit
from merit import Case
import os

cases = [
    Case(
        sut_input_values={"text": "short"},
        tags={"smoke", "fast"},
        metadata={"execution_time_ms": 50}
    ),
    Case(
        sut_input_values={"text": "very long input" * 100},
        tags={"regression", "slow"},
        metadata={"execution_time_ms": 5000}
    ),
]

# Run different subsets based on context
if os.getenv("CI_QUICK"):
    # Fast tests only in CI
    test_cases = [c for c in cases if "fast" in c.tags]
else:
    # Full suite locally
    test_cases = cases

@merit.iter_cases(*test_cases)
def merit_text_processor(case: Case, processor):
    processor(**case.sut_input_values)
```
