Python for AIPython 3.10+ · Pydantic 2.12
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
34 small wins to finish your pathNext lesson →

pytest.raises and parametrize

pytest.raises is a check that passes only when the code inside it raises a given error, and parametrize runs one test with many inputs.

Last updated: 30 Sep, 2026 · Python 3.14 · pytest 9.1 · Pydantic 2.12

The tests in pytest check answers that are right. The code most likely to fail on the job handles answers that are wrong, so it needs tests too.

Syntax:

python
with pytest.raises(SomeError):
    code_that_should_fail()

@pytest.mark.parametrize("value", [a, b, c])
def test_x(value):
    ...

The checking step in its own file

The check from model_validate_json, as a function in parse.py:

Exampleparse.py
from typing import Literal

from pydantic import BaseModel, Field


class Triage(BaseModel):
    category: Literal["billing", "shipping", "other"]
    priority: int = Field(ge=1, le=5)


def parse_answer(answer: str) -> Triage:
    return Triage.model_validate_json(answer)

Expecting an error with pytest.raises

Exampletest_parse.py
import pytest
from pydantic import ValidationError

from parse import parse_answer


def test_good_answer():
    result = parse_answer('{"category": "billing", "priority": 4}')
    assert result.category == "billing"


def test_sentence_is_rejected():
    with pytest.raises(ValidationError):
        parse_answer("I am not sure how to sort this one.")

with pytest.raises(ValidationError): passes only if the code inside raises that error. If parse_answer ever starts accepting sentences, this test fails and says so.

Example
pytest -q

Running one test with many inputs

There are many ways for JSON to be wrong: a category that does not exist, a priority out of range, a field missing. Writing a test function for each repeats the same three lines. Add this to the end of test_parse.py:

Exampleadded to test_parse.py
@pytest.mark.parametrize("answer", [
    '{"category": "sales", "priority": 4}',
    '{"category": "billing", "priority": 9}',
    '{"category": "billing"}',
])
def test_bad_values_are_rejected(answer):
    with pytest.raises(ValidationError):
        parse_answer(answer)

@pytest.mark.parametrize is a decorator, like @dataclass in dataclasses. It runs the test once for each value in the list, passing it in as answer.

Example
pytest -q

Five tests from two functions and one list. Adding a new kind of bad answer, when you meet one in real use, is one line in that list.

Listing the tests with --collect-only

Example
pytest --collect-only -q

--collect-only lists the tests without running them. Each parametrized run is named after the function plus the input in square brackets, so a failure tells you exactly which answer broke it.

try and except vs pytest.raises

try / exceptpytest.raises
Used inYour programYour tests
No error raisedThe program carries onThe test fails
PurposeRecover from the errorProve the error happens

Where these show up in AI code

  • A list of bad model answers you have met, each one a parametrized case.
  • Proving that a validator, a guardrail or a parser refuses what it should.
Watch out. Code after the line that raises, inside the same with pytest.raises block, never runs. Keep one call per block, or a second check will look tested when it is not.
Try it yourself
  • Add '{"category": "billing", "priority": "high"}' to the list.
  • Add '{"category": "billing", "priority": "4"}'. Why does that case fail the test?
  • Change ge=1 to ge=0 in parse.py and run pytest -q. No test notices; add a case with priority 0 to the list so one does.
Previouspytest

You understood something today that you didn't yesterday.