Fluent API¶
Chaining¶
Assertions chain into a single statement that reads like a sentence:
assert_that("foo").is_length(3).starts_with("f").ends_with("oo")
assert_that([1, 2, 3]).is_type_of(list).contains(1, 2).does_not_contain(4, 5)
assert_that(fred).has_first_name("Fred").has_last_name("Smith").has_shoe_size(12)
assert_that(people).is_length(2).extracting("first_name").contains("Fred", "Joe")
Universal negation¶
The .not_ property inverts the next assertion in the chain, so there is no need for dedicated
is_not_* methods:
assert_that(5).not_.is_none()
assert_that("abc123").not_.is_alpha()
assert_that([3, 1, 2]).not_.is_sorted()
assert_that(42).not_.is_in(1, 2, 3)
assert_that("hello").not_.is_instance_of(int)
Chaining continues normally after a negated assertion:
assert_that(5).not_.is_none().is_positive()
assert_that("hello").not_.is_empty().is_length(5).is_alpha()
.not_ works with descriptions, soft assertions, warn mode, and matchers:
assert_that(5).described_as("my check").not_.is_positive()
# AssertionError: [my check] Expected <5> to NOT satisfy: is_positive()
with soft_assertions():
assert_that(5).not_.is_positive() # collected, not raised
assert_warn("hello").not_.is_alpha() # logs a warning
assert_that(-5).not_.satisfies(match.is_positive())
assert_that([1, -2, 3]).not_.each(match.is_positive())
Collection pipeline¶
Pipeline methods transform the value before asserting. Each returns a new builder, so the original value is unchanged and steps chain freely.
filtered_on(predicate)¶
Filter elements by a callable or matcher:
assert_that([1, -2, 3, -4]).filtered_on(lambda x: x > 0).is_length(2)
assert_that(items).filtered_on(match.is_positive()).is_not_empty()
assert_that(users).filtered_on(match.has_property("active")).is_length(5)
mapped(func)¶
Transform each element:
assert_that(["a", "b", "c"]).mapped(str.upper).contains("A", "B")
assert_that(users).mapped(lambda u: u.name).contains("Alice", "Bob")
flat_mapped(func)¶
Transform and flatten:
assert_that(["ab", "cd"]).flat_mapped(list).contains("a", "b", "c", "d")
assert_that(users).flat_mapped(lambda u: u.tags).contains("admin", "user")
first() / last() / element(index) / single()¶
Navigate to a specific element and assert on it:
assert_that([10, 20, 30]).first().is_equal_to(10)
assert_that([10, 20, 30]).last().is_equal_to(30)
assert_that([10, 20, 30]).element(1).is_equal_to(20)
assert_that([42]).single().is_equal_to(42)
Warning
first(), last(), and single() raise ValueError on an empty collection (single() also on
more than one element); element(index) raises IndexError when the index is out of range.
Chaining pipeline steps¶
Pipeline methods return a new builder, so they chain with each other and with any assertion: