Cheatsheet for testing Pydantic models with pytest.

Basic happy path

def test_user_valid():
    user = User.model_validate({"id": 1, "email": "[email protected]", "name": "Alice"})
    assert user.id == 1
    assert user.email == "[email protected]"

Parametrized validation

import pytest
from pydantic import ValidationError

@pytest.mark.parametrize("payload, ok", [
    ({"id": 1, "email": "[email protected]"}, True),
    ({"id": "abc", "email": "[email protected]"}, False),
    ({"email": "[email protected]"}, False),
    ({"id": 1, "email": "not-email"}, False),
])
def test_user_validation(payload, ok):
    if ok:
        User.model_validate(payload)
    else:
        with pytest.raises(ValidationError):
            User.model_validate(payload)

Assert specific error

def test_age_negative():
    with pytest.raises(ValidationError) as exc:
        User.model_validate({"id": 1, "email": "[email protected]", "age": -1})
    errs = exc.value.errors()
    assert any(e["loc"] == ("age",) and "non-negative" in e["msg"] for e in errs)

Match error type

def test_email_format():
    with pytest.raises(ValidationError) as exc:
        User.model_validate({"id": 1, "email": "not-an-email"})
    errs = exc.value.errors()
    assert any(e["type"] == "value_error" for e in errs)

Round-trip

def test_roundtrip(user_factory):
    user = user_factory()
    data = user.model_dump(mode="json")
    restored = User.model_validate(data)
    assert restored == user

Factory (test fixtures)

import pytest
import factory

class UserFactory(factory.Factory):
    class Meta:
        model = User
    
    id = factory.Sequence(lambda n: n + 1)
    email = factory.Sequence(lambda n: f"user{n}@example.com")
    name = factory.Faker("name")

@pytest.fixture
def user_factory():
    return UserFactory

def test_with_factory(user_factory):
    user = user_factory()
    assert user.id > 0

polyfactory (Pydantic-aware)

from polyfactory.factories.pydantic_factory import ModelFactory

class UserFactory(ModelFactory[User]):
    __model__ = User

def test_random():
    user = UserFactory.build()
    assert isinstance(user, User)

Auto-generates random valid instances.

Property-based testing (Hypothesis)

from hypothesis import given, strategies as st

@given(st.integers(min_value=1), st.emails())
def test_user_property(id, email):
    user = User.model_validate({"id": id, "email": email})
    assert user.id == id

Catches edge cases automatically.

Custom error messages

def test_password_complexity():
    with pytest.raises(ValidationError, match="must contain uppercase"):
        Password.model_validate("alllowercase")

match regex against the string repr.

Schema snapshot

def test_schema_snapshot(snapshot):
    schema = User.model_json_schema()
    snapshot.assert_match(json.dumps(schema, indent=2), "user_schema.json")

Catches accidental API breakage. Using syrupy or similar snapshot lib.

Equality

def test_user_equality():
    a = User(id=1, email="[email protected]")
    b = User(id=1, email="[email protected]")
    assert a == b               # value equality

Mutability

def test_frozen():
    class Frozen(BaseModel):
        model_config = {"frozen": True}
        x: int
    
    f = Frozen(x=1)
    with pytest.raises(ValidationError):
        f.x = 2

Test custom validator

def test_custom_validator():
    @field_validator("email", mode="before")
    @classmethod
    def lower(cls, v):
        return v.lower() if isinstance(v, str) else v
    
    user = User.model_validate({"id": 1, "email": "[email protected]"})
    assert user.email == "[email protected]"

Test computed_field

def test_full_name():
    user = User(first="Alice", last="X")
    assert user.full_name == "Alice X"
    assert "full_name" in user.model_dump()

Common mistakes

  • Asserting on internal e.value.__cause__ — not portable; use e.value.errors().
  • Forgetting pytest.raises for failure case — test silently passes.
  • Testing only happy path — edge cases ship.
  • Factory boy without Pydantic awareness — generated data fails Pydantic.

Read this next

If you want my polyfactory + Hypothesis Pydantic test patterns, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .