Skip to main content
https://badge.fury.io/py/pyhamcrest-toolbox.svg Documentation Status Maintainability PyPI Package monthly downloads

PyHamcrest Toolbox

PyHamcrest is a great thing for writing reusable tests, but sometimes writing your own matchers may be a bit of a chore. This project aims at making this task easy, fast and even fun (for want of a better word).

To reiterate the obvious, a test should always do all the checks it has to do, and even if some of them fail, the rest should still be run. That way you will get a better idea of what’s gone wrong with your code, and you will waste less time fixing the first of the checks only to find the the second one is still failing, and that means that you should have fixed the first one in a different way.

So, instead of this:

def test_the_holy_grail():
    the_holy_grail = seek_the_holy_grail()
    assert_that(the_holy_grail.is_holy(), is_(True))
    assert_that(the_holy_grail.depth, greater_than(5))
    assert_that(the_holy_grail.width, greater_than(6))
    assert_that(the_holy_grail.height, greater_than(7))

this should be written as:

def test_the_holy_grail():
    the_holy_grail = seek_the_holy_grail()
    assert_that(
        the_holy_grail,
        is_holy()
            .with_depth(greater_than(5))
            .with_width(greater_than(6))
            .with_height(greater_than(7))
    )

or:

def test_the_holy_grail():
    the_holy_grail = seek_the_holy_grail()
    assert_that(
        the_holy_grail,
        grail(holy=True, width=5))

Both these examples, however, require writing your own matchers. With this toolbox, it is easy.

MulticomponentMatcher

The MulticomponentMatcher allows writing the chain-style matchers.

All you have to do is to write your is_holy matcher that inherits from the MulticomponentMatcher as the backbone. Then you write individual matchers for each of the holy grail properties enhancing them with the MatcherPlugin, and you register them with the is_holy matcher.

So, this is your is_holy matcher:

class IsHolyMatcher(MulticomponentMatcher):
    def __init__(self):
        super().__init__()

def is_holy():
    return IsHolyMatcher()

And that’s it. You don’t have to override the usual matcher methods. Everything will be done by the parent class. However, it doesn’t do any matching yet, so we need to write the plugins. Let’s start with the actual holiness:

class HolinessMatcher(MatcherPlugin):
    def __init__(is_holy=True):
        super().__init__()
        self.is_holy = is_holy

    def component_matches(self, item):
        # the item will be a grail
        return self.is_holy == item.is_holy()

    def describe_to(self, description):
        description.append_text(
            "A grail which is {}holy".format("" if self.is_holy else "not "))

    def describe_component_mismatch(self, item, mismatch_description):
        mismatch_description.append_text(
            "The grail was {}holy".format("" if item.is_holy() else "not "))

And then you register it with the main matcher:

class IsHolyMatcher(MulticomponentMatcher):
    def __init__(self, is_holy):
        super().__init__()
        self.register(HolynessMatcher(is_holy))

def holy(is_holy):
    return IsHolyMatcher(is_holy)

Of course, you could write that HolinessMatcher logic in your IsHolyMatcher, but if we have the power of plugins, then why not use it?

For now, we only have this bit: assert_that(the_grail, is_holy()), and not the .with_width(...) stuff. So let’s write it. I won’t go through the process of writing the plugin for the width as it is rather straightforward, but here’s how you register it with the main matcher:

class IsHolyMatcher(MulticomponentMatcher):
    def __init__(self, is_holy):
        super().__init__()
        self.register(HolinessMatcher(is_holy))

    def with_width(self, value):
        return self.register(GrailWidthMatcher(value))

def holy(is_holy):
    return IsHolyMatcher(is_holy)

Now you can do the is_holy().with_width(greater_than(5)) stuff. Note that you have to return self.register(...) from the plugin registering methods, as (a) you might want to chain them, and (b) the result of the chain still needs to be a matcher.

KwargMulticomponentMatcher

This matcher allows writing the kwarg-style matchers (as in the second example above), which are more pythonic, but look kind of unnatural when you want to match against another matcher instead of a plain value. I will show what I mean in a minute.

The general approach is the same as with the multicomponent matcher: you write matcher plugins for your components, and then you register them with your main matcher:

class GrailMatcher(KwargMulticomponentMatcher):
    def __init__(self, holy=None, width=None):
        self.register_for_kwarg(HolinessMatcher(holy), holy)
        self.register_for_kwarg(GrailWidthMatcher(width), width)

And then in your tests you do:

def test_correct_width_wrong_holiness(self, my_grail):
    assert_that(
        my_grail,
        grail(holy=True, width=4))

As I said before, this looks more pythonic, however, if you want to check your values against matchers, and not just plain values (like width=4 here), your code starts looking a bit strange:

def test_correct_width_wrong_holiness(self, my_grail):
    assert_that(
        my_grail,
        grail(holy=True, width=greater_than(4)))

My recommendation is to use the chain-style matchers if you know that your main matcher might be used this way.

Demos

You can find the demos for both approaches in the demo folder of this repo. Clone it, install the requirements from demo/requirements.txt, and run pytest demo/test_*

Metadata

Release files for pyhamcrest-toolbox 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyhamcrest-toolbox 0.3.0
File Size Uploaded Provenance
pyhamcrest_toolbox-0.3.0.tar.gz 6.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pyhamcrest-toolbox 0.3.0
File Interpreter ABI Platform
pyhamcrest_toolbox-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 13.7 kB

Release files / pyhamcrest_toolbox-0.3.0.tar.gz

Download URL pyhamcrest_toolbox-0.3.0.tar.gz
Size 6.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a6d366e4d4463e38feb07c3a1b8d1fb0465266ec1b77814841119fad3935d0ac
BLAKE2b-256 checksum
How to use checksums
0ff396b05755128ed4c5a4fc1256455aa4b8015f5c86f180ada11ccf51dc0936
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.0

Release files / pyhamcrest_toolbox-0.3.0-py3-none-any.whl

Download URL pyhamcrest_toolbox-0.3.0-py3-none-any.whl
Size 7.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a1329907d314748c97f0a7e2f2e7b64ca6cad9891693f7862ee1297cfa6fafdf
BLAKE2b-256 checksum
How to use checksums
89ab014b5b5403167222e31877b52ecde9144a950a9efff512a88bd0b7070813
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.0

Security for pyhamcrest-toolbox 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page