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 Matcher as M

Installation

pip install git+https://github.com/Suor/assert-matcher.git@master
# pip install assert-matcher  # not released yet

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),
]

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)

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)

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.7+ and PyPy3. You can run it for particular environment even in absense of all of the above:

tox -e py310
tox -e pypy3
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 Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

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

assert_matcher-0.9-py2.py3-none-any.whl (10.1 kB view details)

Uploaded Python 2Python 3

File details

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

File metadata

  • Download URL: assert_matcher-0.9-py2.py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for assert_matcher-0.9-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 b186d655a6becfb1818cefd732060872efd0a00e5dd3d9aa86e937e6f28ccb31
MD5 7fc660940c3cde90d3f7eacef179f040
BLAKE2b-256 067a4f3a07c62d8c6700c65ea9217907b155e12757e8096c78c134a7a182d3a5

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