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.
-
Typed narrowing & contracts
An assertion returns the value it checked, statically narrowed to the asserted type - so
.valueis typed, with nocastand no bareassert.assert_conforms()does the same against a Pydantic model. -
Composable matchers
Predicate matchers you combine with the
&,|, and~operators and reuse across assertions - or register your own. -
Structural matching
Declarative validation of dicts, Pydantic models, and API responses, with the exact path to each mismatch on failure.
-
Soft & async assertions
Collect multiple failures in one run. Poll for eventual consistency with
eventually()(async) oreventually_sync()(blocking). -
Expected exceptions
Assert that a call raises, then chain onto the message, the cause chain, an
ExceptionGroup, or the exception object itself. -
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. -
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. -
Assertions as values
check()runs the next assertion for its verdict instead of an exception, handing back anAssertionOutcomethat carries the message, the values and a walkable diff. -
Snapshot testing
External-file, in-source, and value-tolerant contract snapshots - all three under one selective, typed API, with inline recording under
pytest-xdist. -
Data frames & arrays
pandas / polars / numpy data-frame and array assertions, alongside Allure and Behave integrations.
-
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.
-
The assertion that checked nothing
all_satisfyover an empty collection passes, the wayall([])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 of your own
add_extension()puts an assertion you wrote on every chain, and aMatcher[T]handed tosatisfies()keeps it typed, so the checker follows your own vocabulary too.
Install¶
Optional extras:
assertpy[json]- JSONPath, JSON Schema, and OpenAPI contractsassertpy[inline]- inline snapshots (matches_inline())assertpy[data]- pandas, polars and numpy, or each under its own nameassertpy[allure]- Allure reportingassertpy[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),
}),
})
See Quickstart to dive in, or browse Type assertions, Matchers, and the rest of the navigation for the full set of assertions and integrations.