Entry points¶
The top-level functions exported by assertpy2.
assert_that is statically overloaded: the return type narrows to the assertions valid for the
value's type. Each per-type overload is listed below. See Type safety for how this
drives editor autocomplete.
assert_that ¶
Set the value to be tested, plus an optional description, and allow assertions to be called.
This is a factory method for the AssertionBuilder, and the single most important
method in all of assertpy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
val
|
object
|
the value to be tested (aka the actual value) |
required |
description
|
str
|
the extra error message description. Defaults to |
''
|
Examples:
Just import it once at the top of your test file, and away you go...
from assertpy2 import assert_that
def test_something():
assert_that(1 + 2).is_equal_to(3)
assert_that('foobar').is_length(6).starts_with('foo').ends_with('bar')
assert_that(['a', 'b', 'c']).contains('a').does_not_contain('x')
Source code in assertpy2/assertpy.py
value
property
¶
The value under test, returned as-is for typed extract-and-continue.
Ends a chain by handing the checked value back, so a test can keep using it after the
assertions passed. For object- and union-typed values the static type is refined by the
narrowing assertions along the way:
is_not_none() removes None and
is_instance_of() narrows to the checked class, so
no cast() or bare assert is needed to satisfy a type checker.
value is a strict-mode extraction: it hands the value back only when every assertion on
it passed. If one failed under
soft_assertions() or
assert_warn() - where failures are collected, not raised -
reading value raises TypeError instead of returning an unverified value. Read it in
strict mode, or after the soft block has closed.
The taint is per-value, not per-chain. A value-changing pivot
(extracting(),
first(),
decoded_as(), ...) starts a new value with a
fresh guard and validates its own input, so a pivot never reaches .value with a value
derived from a failed assertion - it raises in the pivot first.
Examples:
Usage:
order: Order | None = repo.find_order(42)
paid = assert_that(order).is_not_none().is_instance_of(PaidOrder).value
paid.refund() # statically typed as PaidOrder
Returns:
| Name | Type | Description |
|---|---|---|
object |
_T
|
the original value under test (never a copy) |
Raises:
| Type | Description |
|---|---|
TypeError
|
if an assertion on this chain failed under |
check ¶
Run the next assertion for its verdict instead of for its failure.
The assertion does not raise, collect or log. It returns an
AssertionOutcome, truthy when it held and carrying the
failure message, values and diff when it did not.
For asking a question about a value. An assertion states a requirement, and a test that stops at the first unmet one is the point; this is for the cases that are not that, like branching on a precondition, or reporting a check into a system that is not pytest.
A bad argument still raises. TypeError and ValueError mean the call itself is wrong,
which is not a verdict about the value and would be silenced by returning one.
Examples:
Usage:
outcome = assert_that(response).check().is_equal_to(expected)
if not outcome:
logger.warning(outcome.message)
assert_that(5).check().is_positive().passed # True
assert_that(5).check().not_.is_positive().passed # False
Source code in assertpy2/assertpy.py
CheckBuilder ¶
Proxy returned by check().
Runs one assertion with the builder in verdict mode and hands back what it decided. The mode is put on and taken off around the call rather than held, so a builder that is also used normally afterwards is unaffected, and an assertion that raises for a bad argument still leaves it clean.
not_ is proxied rather than refused, so a negated assertion can be asked for a verdict too.
Anything else that is not callable - val, description - is handed straight back.
Source code in assertpy2/assertpy.py
AssertionOutcome
dataclass
¶
AssertionOutcome(
*,
passed: bool = False,
message: str = "",
actual: object = None,
actual_provided: bool = False,
expected: object = MISSING,
diff: DiffResult | None = None,
trace: PollTrace | None = None,
group: str | None = None,
location: tuple[str, int] | None = None,
hint: str | None = None,
)
What one assertion decided, as a value rather than as a raised exception.
Returned by check(), which runs an assertion for its
verdict instead of for its failure. Truthy when the assertion passed, so it reads as the answer to
the question it was asked.
passed
class-attribute
instance-attribute
¶
Whether the assertion held.
False on every record the failure path builds, which is all of them until something asks for a
verdict: a failure is composed only when there is one.
message
class-attribute
instance-attribute
¶
The full failure text, description prefix and all, exactly as it reaches the reader.
Empty on a passing outcome. There is no message for an assertion that held, and inventing one would put words in the report of anything that logs whatever it is handed.
actual
class-attribute
instance-attribute
¶
The value under test. Filled from the builder when the assertion did not name one itself, so
every failure carries it, and actual_provided says which of the two happened.
actual_provided
class-attribute
instance-attribute
¶
Whether the assertion passed actual itself rather than having it filled in.
Read by anything that renders: a value the assertion chose to name is worth showing, one filled in from the builder is usually already in the message.
trace
class-attribute
instance-attribute
¶
The convergence telemetry of a poll that timed out.
Here rather than only on the exception because a polling assertion under a soft block or in warn mode never builds one: it goes through the same delivery as everything else, and the trace used to stop at that boundary.
group
class-attribute
instance-attribute
¶
The label a soft block was grouping under when this was collected.
Only a soft block groups, so this is None everywhere else, including on a failure that was
raised. Kept on the record rather than beside it so a collected failure stays one thing.
location
class-attribute
instance-attribute
¶
The (file, line) of the caller, on a failure that was collected rather than raised.
None on a raised failure, whose traceback is the better answer, and where finding this costs a
walk of the whole stack that nothing would read.
hint
class-attribute
instance-attribute
¶
The diagnostic line, kept apart from message as well as glued into it.
It is glued in because that is where a reader needs it, and kept apart because once it is part of the string nothing downstream can tell it from the assertion's own words.
has_expected
property
¶
Whether an expected value was named at all, which expected is not None cannot answer.
assert_conforms ¶
Validate val against a pydantic v2 model and continue over the validated instance.
The narrowing-complete companion to assert_that() for
contract testing. Runs model.model_validate(val): on success the returned builder carries the
validated, coerced instance (so .value and extracting see typed fields); on failure it
fails with pydantic's validation errors.
Because the return type is driven by model rather than by the type of val, the chain
narrows to model for any input - including the Any a decoded JSON payload carries.
With exact=True it also asserts contract drift: the payload must not carry fields the model
does not declare. model_validate silently drops undeclared fields, so a stale model keeps
passing after the live API grows new ones.
exact catches that drift - recursively, into nested sub-models and lists - and reports the
exact paths. It is alias-aware, and respects a model that opts into extras (extra="allow").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
val
|
object
|
the raw payload to validate (e.g. a decoded JSON response) |
required |
model
|
type[_U]
|
a pydantic v2 model class (anything exposing |
required |
description
|
str
|
the extra error message description. Defaults to |
''
|
exact
|
bool
|
also fail if the payload carries fields |
False
|
each
|
bool
|
validate a list payload element-by-element against |
False
|
Examples:
Usage:
from assertpy2 import assert_conforms, assert_that
order = assert_conforms(response.json(), OrderModel).value # .value: OrderModel
assert_that(order.total).is_greater_than(0)
# catch silent API growth: fail if the response grew fields the model does not declare
assert_conforms(response.json(), OrderModel, exact=True)
# a list endpoint: validate every item, narrowing to list[OrderModel]
orders = assert_conforms(response.json(), OrderModel, each=True).value # .value: list[OrderModel]
Returns:
| Name | Type | Description |
|---|---|---|
AssertionBuilder |
AssertionBuilder[Any]
|
a builder over the validated model instance, statically typed as |
Raises:
| Type | Description |
|---|---|
TypeError
|
if |
AssertionError
|
if |
Source code in assertpy2/assertpy.py
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | |
assert_warn ¶
Set the value to be tested, and optional description and logger, and allow assertions to be called, but never fail, only log warnings.
This is a factory method for the AssertionBuilder, but unlike assert_that() an
AssertionError is never raised, and execution is never halted. Instead, any assertion failures
results in a warning message being logged. Uses the given logger, or defaults to a simple logger
that prints warnings to stdout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
val
|
object
|
the value to be tested (aka the actual value) |
required |
description
|
str
|
the extra error message description. Defaults to |
''
|
logger
|
Logger
|
the logger for warning message on assertion failure. Defaults to |
None
|
Examples:
Usage:
from assertpy2 import assert_warn
assert_warn('foo').is_length(4)
assert_warn('foo').is_empty()
assert_warn('foo').is_false()
assert_warn('foo').is_digit()
assert_warn('123').is_alpha()
Even though all of the above assertions fail, AssertionError is never raised and
test execution is never halted. Instead, the failed assertions merely log the following
warning messages to stdout:
2019-10-27 20:00:35 WARNING [test_foo.py:23]: Expected <foo> to be of length <4>, but was <3>.
2019-10-27 20:00:35 WARNING [test_foo.py:24]: Expected <foo> to be empty string, but was not.
2019-10-27 20:00:35 WARNING [test_foo.py:25]: Expected <False>, but was not.
2019-10-27 20:00:35 WARNING [test_foo.py:26]: Expected <foo> to contain only digits, but did not.
2019-10-27 20:00:35 WARNING [test_foo.py:27]: Expected <123> to contain only alphabetic chars, but did not.
Tip
Use assert_warn() if and only if you have a really good reason to log assertion
failures instead of failing.
Source code in assertpy2/assertpy.py
soft_assertions ¶
Create a soft assertion context.
Normally, any assertion failure will halt test execution immediately by raising an error. Soft assertions are way to collect assertion failures (and failure messages) together, to be raised all at once at the end, without halting your test.
Uses contextvars internally, so each thread and each asyncio task gets its own
independent soft-assertion state.
Examples:
Create a soft assertion context, and some failing tests:
from assertpy2 import assert_that, soft_assertions
with soft_assertions():
assert_that('foo').is_length(4)
assert_that('foo').is_empty()
assert_that('foo').is_false()
assert_that('foo').is_digit()
assert_that('123').is_alpha()
When the context ends, any assertion failures are collected together and a single
AssertionError is raised, each tagged with the file:line it came from:
AssertionError: soft assertion failures:
1. Expected <foo> to be of length <4>, but was <3>. [test_str.py:10]
2. Expected <foo> to be empty string, but was not. [test_str.py:11]
3. Expected <False>, but was not. [test_str.py:12]
4. Expected <foo> to contain only digits, but did not. [test_str.py:13]
5. Expected <123> to contain only alphabetic chars, but did not. [test_str.py:14]
Group errors by section:
with soft_assertions() as sa:
with sa.group("Headers"):
assert_that(headers["Content-Type"]).is_equal_to("application/json")
with sa.group("Body"):
assert_that(body["status"]).is_equal_to("ok")
Note
The soft assertion context only collects assertion failures, other errors such as
TypeError or ValueError are always raised immediately. Triggering an explicit test
failure with fail() will similarly halt execution immediately.
If you need more forgiving behavior, use soft_fail() to add
a failure message without halting test execution.
Source code in assertpy2/assertpy.py
assert_all ¶
Run all callables inside a soft assertion context.
A convenience wrapper around soft_assertions() for inline use.
Examples:
Usage:
from assertpy2 import assert_all, assert_that
assert_all(
lambda: assert_that(x).is_positive(),
lambda: assert_that(y).is_not_none(),
)
Raises:
| Type | Description |
|---|---|
AssertionError
|
if any of the callables produce assertion failures |
Source code in assertpy2/assertpy.py
fail ¶
Force immediate test failure with the given message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
str
|
the failure message. Defaults to |
''
|
Examples:
Fail a test:
from assertpy2 import assert_that, fail
def test_fail():
fail('forced fail!')
If you wanted to test for a known failure, here is a useful pattern:
import operator
def test_adder_bad_arg():
try:
operator.add(1, 'bad arg')
fail('should have raised error')
except TypeError as e:
assert_that(str(e)).contains('unsupported operand')
Source code in assertpy2/assertpy.py
soft_fail ¶
Within a soft_assertions() context, append the failure
message to the soft error list, but do not halt test execution.
Otherwise, outside the context, acts identical to fail() and forces immediate test
failure with the given message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
str
|
the failure message. Defaults to |
''
|
Examples:
Failing soft assertions:
from assertpy2 import assert_that, soft_assertions, soft_fail
with soft_assertions():
assert_that(1).is_equal_to(2)
soft_fail('my message')
assert_that('foo').is_equal_to('bar')
Fails, and outputs the following soft error list (each tagged with its file:line):
AssertionError: soft assertion failures:
1. Expected <1> to be equal to <2>, but was not. [test_add.py:10]
2. Fail: my message! [test_add.py:11]
3. Expected <foo> to be equal to <bar>, but was not. [test_add.py:12]
Source code in assertpy2/assertpy.py
add_extension ¶
Add a new user-defined custom assertion to assertpy.
Once the assertion is registered with assertpy, use it like any other assertion. Pass val to
assert_that(), and then call it.
A name already in use is refused, so an extension that would quietly replace a built-in assertion
or another extension says so instead. Registering the same implementation again is not a clash:
a module-scoped conftest fixture rebuilds its function on every module that requests it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
the assertion function (to be added) |
required |
override
|
bool
|
replace an assertion of the same name instead of refusing |
False
|
Raises:
| Type | Description |
|---|---|
TypeError
|
if |
ValueError
|
if its |
Examples:
Usage:
from assertpy2 import add_extension
def is_5(self):
if self.val != 5:
return self.error(f'{self.val} is NOT 5!')
return self
add_extension(is_5)
def test_5():
assert_that(5).is_5()
def test_6():
assert_that(6).is_5() # fails
# 6 is NOT 5!
Source code in assertpy2/assertpy.py
remove_extension ¶
Remove a user-defined custom assertion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
the assertion function (to be removed) |
required |
Examples:
Usage:
from assertpy2 import remove_extension
remove_extension(is_5)