Skip to content

assertpy

Fluent, fully type-aware assertions for Python.

The type checker follows every assert_that(...) chain to the end, and each assertion hands back the value it checked, statically narrowed.

from assertpy import assert_that

# port is a typed int - the value the chain checked, narrowed
port = assert_that(8080).is_instance_of(int).is_positive().value

Why assertpy

  • Typed overloads


    String methods on strings, numeric methods on numbers - the type checker rejects the wrong ones before the test even runs. The core advantage over assertpy 1.x and most alternatives.

    Type safety

  • Typed narrowing & contracts


    An assertion returns the value it checked, statically narrowed to the asserted type - so .value is typed, with no cast and no bare assert. assert_conforms() does the same against a Pydantic model.

    Typed narrowing

  • Composable matchers


    Predicate matchers you combine with the &, |, and ~ operators and reuse across assertions - or register your own.

    Matchers

  • Structural matching


    Declarative validation of dicts, Pydantic models, and API responses, with the exact path to each mismatch on failure.

    Structural matching

  • Soft & async assertions


    Collect multiple failures in one run. Poll for eventual consistency with eventually() (async) or eventually_sync() (blocking).

    Testing

  • Expected exceptions


    Assert that a call raises, then chain onto the message, the cause chain, an ExceptionGroup, or the exception object itself.

    Errors & reporting

  • Structured failures


    Failures carry machine-readable .actual, .expected, and .diff. The diff renders into the message itself and, under pytest, as colored sections for dicts, dataclasses, attrs, and Pydantic models.

    Errors & reporting

  • Failures that say why


    Where one fact explains a failure, a line under the message says it: an id held as text, a NaN, two instances compared by identity. A failed contains() names the closest row.

    Some failures say why

  • Assertions as values


    check() runs the next assertion for its verdict instead of an exception, handing back an AssertionOutcome that carries the message, the values and a walkable diff.

    Asking instead of asserting

  • Snapshot testing


    External-file, in-source, and value-tolerant contract snapshots - all three under one selective, typed API, with inline recording under pytest-xdist.

    Snapshots

  • Data frames & arrays


    pandas / polars / numpy data-frame and array assertions, alongside Allure and Behave integrations.

    Data frames and arrays

  • JSON & API assertions


    Navigate JSON with JSON Path, validate it against a JSON Schema or an OpenAPI response contract, and pull values out with regex groups - built for API and service tests.

    JSON & data

  • The assertion that checked nothing


    all_satisfy over an empty collection passes, the way all([]) is true. An opt-in guard says so, turning the silent false pass a green run never reveals into a warning you can escalate.

    Assertions that checked nothing

  • Assertions of your own


    add_extension() puts an assertion you wrote on every chain, and a Matcher[T] handed to satisfies() keeps it typed, so the checker follows your own vocabulary too.

    Custom assertions

Install

pip install assertpy

Optional extras:

  • assertpy[json] - JSONPath, JSON Schema, and OpenAPI contracts
  • assertpy[inline] - inline snapshots (matches_inline())
  • assertpy[data] - pandas, polars and numpy, or each under its own name
  • assertpy[allure] - Allure reporting
  • assertpy[behave] - Behave step matchers

Quick example

from assertpy import assert_that, match

assert_that("foobar").is_length(6).starts_with("foo").ends_with("bar")
assert_that([1, 2, 3]).contains(1).is_subset_of([1, 2, 3, 4])
assert_that({"id": 1, "name": "Alice"}).matches_structure(
    {"id": match.is_instance_of(int), "name": match.is_non_empty_string()}
)

When a check fails, the pytest plugin points at the exact field instead of dumping both structures:

assert_that(response).matches_structure({
    "user": match.structure({
        "name": match.is_non_empty_string(),
        "role": match.is_in("admin", "user"),
        "age": match.between(18, 120),
    }),
})

Structured diff in the terminal: every failing field with its path and the predicate that failed, in color

See Quickstart to dive in, or browse Type assertions, Matchers, and the rest of the navigation for the full set of assertions and integrations.