Usage
Boolean Logic¶
dirty-equals types can be combined based on either &
(and, all checks must be True
for the combined check to be True
) or |
(or, any check can be True
for the combined check to be True
).
Types can also be inverted using the ~
operator, this is equivalent to using !=
instead of ==
.
Example:
from dirty_equals import Contains, HasLen
assert ['a', 'b', 'c'] == HasLen(3) & Contains('a') # (1)!
assert ['a', 'b', 'c'] == HasLen(3) | Contains('z') # (2)!
assert ['a', 'b', 'c'] != Contains('z')
assert ['a', 'b', 'c'] == ~Contains('z')
- The object on the left has to both have length 3 and contain
"a"
- The object on the left has to either have length 3 or contain
"z"
Initialised vs. Class comparison¶
Warning
This does not work with PyPy.
dirty-equals allows comparison with types regardless of whether they've been initialised.
This saves users adding ()
in lots of places.
Example:
from dirty_equals import IsInt
# these two cases are the same
assert 1 == IsInt
assert 1 == IsInt()
Note
Types that require at least on argument when being initialised (like IsApprox
)
cannot be used like this, comparisons will just return False
.
__repr__
and pytest compatibility¶
dirty-equals types have reasonable __repr__
methods, which describe types and generally are a close match
of how they would be created:
from dirty_equals import IsApprox, IsInt
assert repr(IsInt) == 'IsInt'
assert repr(IsInt()) == 'IsInt()'
assert repr(IsApprox(42)) == 'IsApprox(approx=42)'
However, the repr method of types changes when an equals (==
) operation on them returns a True
, in this case
the __repr__
method will return repr(other)
.
from dirty_equals import IsInt
v = IsInt()
assert 42 == v
assert repr(v) == '42'
This black magic is designed to make the output of pytest when asserts on large objects fail as simple as possible to read.
Consider the following unit test:
from datetime import datetime
from dirty_equals import IsNow, IsPositiveInt
def test_partial_dict():
api_response_data = {
'id': 1, # (1)!
'first_name': 'John',
'last_name': 'Doe',
'created_at': datetime.now().isoformat(),
'phone': '+44 123456789',
}
assert api_response_data == {
'id': IsPositiveInt(),
'first_name': 'John',
'last_name': 'Doe',
'created_at': IsNow(iso_string=True),
# phone number is missing, so the test will fail
}
- For simplicity we've hardcoded
id
here, but in a test it could be any positive int, hence why we needIsPositiveInt()
Here's an except from the output of pytest -vv
show the error details:
E Common items:
E {'created_at': '2022-02-25T15:41:38.493512',
E 'first_name': 'John',
E 'id': 1,
E 'last_name': 'Doe'}
E Left contains 1 more item:
E {'phone': '+44 123456789'}
E Full diff:
E {
E 'created_at': '2022-02-25T15:41:38.493512',
E 'first_name': 'John',
E 'id': 1,
E 'last_name': 'Doe',
E + 'phone': '+44 123456789',
E }
It's easy to see that the phone
key is missing, id
and created_at
are represented by the exact
values they were compared to, so don't show as different in the "Full diff" section.
Warning
This black magic only works when using initialised types, if IsPositiveInt
was used instead IsPositiveInt()
in the above example, the output would not be as clean.