Skip to main content

Makes comparing nested data structures easy and glancable

Project description

Assert Matcher Build Status

The purpose of this library is to simplify asserts containing objects and nested data structures, i.e.:

def test_smth(M):  # A special Matcher fixture
    # ...
    assert result.errors == [
        M(message=M.re("^Smth went wrong:"), extensions=M.dict(code=523)),
        M(message=M.any, tags=M.unordered("one", "two")),
    ]

Here all the structures like lists and dicts are followed as usual both outside and inside a matcher object, while the matcher object and other helpers provide their own equality. These could be freely intermixed.

Alternatively may import Matcher directly this is more verbose but also more universal, i.e. works outside of pytest:

from assert_matcher import M

Installation

pip install assert-matcher
pip install git+https://github.com/Suor/assert-matcher.git@master  # the newest one

Matchers

The M fixture provides a number of matchers to help you write more expressive assertions.

M(**attrs)

Matches an object's attributes.

User = namedtuple("User", ["name", "email", "is_active"])

users = [User("Alice", "alice@example.com", True), User("Bob", "bob@example.com", False)]

assert users == [
    M(name="Alice", is_active=True),
    M(name="Bob", is_active=False),
]

# May test class if that is important
assert user == M(__class__=User, name="Alice")

M.re(pattern, flags=0)

Matches a string against a regular expression using re.search.

assert "hello world" == M.re(r"h.*o")

M.dict(d=None, **kwargs)

Matches a dictionary against a subset of keys. It checks that the keys exist and their values match, but ignores other keys.

assert {"a": 1, "b": 2} == M.dict(a=1)
assert {"a": 1, "b": 2} == M.dict({"a": 1})  # same

M.any

Matches any value. This is useful when you want to ignore a particular field value in a larger data structure, but still want to test for a key presence.

assert {"a": 1, "b": 2, "c": "hi"} == M.dict(a=1, b=M.any)

M.any_of(*items)

Matches if the value is one of the provided items.

assert "a" == M.any_of("a", "b", "c")

M.unordered(*items)

Compares a list to the provided items, ignoring the order.

assert [1, 2, 3] == M.unordered(3, 1, 2)

M.isa(*types)

Checks if an object is an instance of one of the provided types.

assert "hello" == M.isa(str)
assert 42 == M.isa(int, float)

M.approx(expected, rel=None, abs=None, nan_ok=False)

Performs approximate comparison of numbers. It's a wrapper around pytest.approx.

assert 0.1 + 0.2 == M.approx(0.3)
assert end_time == M.approx(start_time + timedelta(seconds=1), abs=0.1)

Running tests

To run the tests using your default python:

pip install -r test_requirements.txt
pytest

To fully run tox you need all the supported pythons to be installed. These are 3.9+ and PyPy3. You can run it for particular environment even in absense of all of the above:

tox -e py314
tox -e pypy311
tox -e lint

Pytest Plugin

assert-matcher provides enhanced assertion representation for pytest, making diffs for nested data structures more readable when using Matcher objects.

When a test fails, pytest usually prints a diff of the two objects that were compared. However, these diffs can be hard to read. assert-matcher plugin overrides the default diff representation to pinpoint an exact path and difference.

For example, consider the following failing test:

def test_user(M):
    user = User("Alice", "alice@example.com", True)
    assert user == M(name="Bob", is_active=False)

Without the plugin, the diff would look something like this:

E   AssertionError: assert User(name='Alice', email='alice@example.com', is_active=True) == M(name='Bob', is_active=False)
E    +  where M(name='Bob', is_active=False) = <class 'assert_matcher.matchers.Matcher'>(name='Bob', is_active=False)

With the assert-matcher plugin enabled, the diff is much more readable:

E   AssertionError: assert User(name=...tive=True) == M(name='Bo...ive=False)
E     name: 'Alice' != 'Bob'
E     is_active: True != False

This is even more useful when you compare nested data structures. For example, consider the following test:

def test_nested_structure(M):
    result = {
        "users": [alice, bob],
        "meta": ...
    }
    expected = {
        "users": [
            M(name="Alice", status="active"),
            M(name="Bob", email=M.re(r"@example\.com$")),
        ],
        "meta": M.any
    }
    assert result == expected

Without the plugin, the diff is hard to read (imagine these objects having 10+ fields):

E   AssertionError: assert {'meta': {'er... 'inactive'}]} == {'meta': M.di...ample.com$')]}
E
E     Omitting 1 identical items, use -vv to show
E     Differing items:
E     {'users': [{'email': 'alice@example.com', 'name': 'Alice', 'status': 'active'}, {'email': 'bob@invalid', 'name': 'Bob', 'status': 'inactive'}]} != {'users': [M.dict(name='Alice', status='active'), M.dict(name='Bob', email=r'@example.com$')]}
E     Use -v to get more diff

With the plugin, the diff is much more concise and points to the exact location of the error:

E   AssertionError: assert {'meta': {...active'}} == {'meta': M...e.com$')]}
E     users[1].email: 'bob@invalid' != r'@example.com$'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

assert_matcher-0.9.1.tar.gz (12.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

assert_matcher-0.9.1-py2.py3-none-any.whl (10.3 kB view details)

Uploaded Python 2Python 3

File details

Details for the file assert_matcher-0.9.1.tar.gz.

File metadata

  • Download URL: assert_matcher-0.9.1.tar.gz
  • Upload date:
  • Size: 12.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for assert_matcher-0.9.1.tar.gz
Algorithm Hash digest
SHA256 4760a820bb55f989acc6c790659eb23307b32ed329cd4fdb0a95629138888cbd
MD5 d41c73bed817756cf28fe24bcbdbb60e
BLAKE2b-256 5a5f89bda6b1a7f37d4804db367f8b21b2fc2e600dc9ed20b05b3cc2225100cc

See more details on using hashes here.

File details

Details for the file assert_matcher-0.9.1-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for assert_matcher-0.9.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 c4917ca56ce6c4836e233d6e4407e44790cd9a0de24a5dba0d489307bb361efe
MD5 8391550b532d138cf279cf61b8212dbe
BLAKE2b-256 0ba400013264fb4d0ea9772e7929afacef8a7498fbb16fb925777f0321f4faac

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page