Open-source project
hamcrest/PyHamcrest avatar
hamcrest/PyHamcrest

PyHamcrest gives Python tests a vocabulary of matchers instead of a pile of asserts

Hamcrest matchers for Python

805 stars118 forksPythonNOASSERTION

At a glance

What is it?
PyHamcrest is a Python port of the Hamcrest framework, providing matcher objects so that a test states which aspect of a value matters and to what precision, rather than encoding the whole value in one assertion. It is BSD-3-Clause licensed, classified as production stable, and its current release is V2.1.0 from 2023-10-22.
Who is it for?
PyHamcrest fits a test suite that has grown past the point where assertEqual on a whole object is useful, because a matcher names the aspect under test, and a failure message says what was expected rather than printing two large structures. It is the wrong choice for a small suite where plain asserts read perfectly well, and the wrong choice if you want the failures to look like pytest's own output.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem matchers solve: too specific, or too vague

PyHamcrest is a framework for writing matcher objects, which lets you declaratively define match rules. The README is explicit about the situations where they are invaluable, including UI validation and data filtering, and it says they are most commonly used in the area of writing flexible tests.

The reason is a balance that every test author faces. Overspecifying a test makes it brittle to changes, and underspecifying it makes the test less valuable, since it continues to pass even when the thing being tested is broken. A tool that lets you pick out precisely the aspect under test and describe the values it should have, to a controlled level of precision, produces tests that are just right: they fail when the behaviour under test deviates, and they keep passing when minor unrelated changes are made.

That is the whole argument for the library, and it is a good one. The cost is that assertions stop looking like Python. A reader who does not know Hamcrest has to learn that the first argument is the thing under test, the second is the matcher, and an optional third is a description of the value for the failure message.

The project is the Python port of Hamcrest, whose home is hamcrest.org, and it sits under the hamcrest GitHub organisation. The classifier in the package metadata calls the project production stable, and it is a BSD-3-Clause licensed package with the licence text in LICENSE.txt, even though GitHub's own classification of the repository licence is recorded as one it cannot categorise.

assert_that and the failure message it produces

The first example in the README replaces unittest's assertEqual with PyHamcrest's assert_that and a standard matcher, and the test class is otherwise an ordinary unittest.TestCase.

python
 from hamcrest import assert_that, equal_to
 import unittest


 class BiscuitTest(unittest.TestCase):
     def testEquals(self):
         theBiscuit = Biscuit("Ginger")
         myBiscuit = Biscuit("Ginger")
         assert_that(theBiscuit, equal_to(myBiscuit))


 if __name__ == "__main__":
     unittest.main()

The assert_that function is described in the README as a stylised sentence for making a test assertion. The subject of the assertion is the first parameter, here theBiscuit, and the second is a matcher, in this case one that checks one object is equal to another using the Python == operator. The test passes because the Biscuit class defines an __eq__ method.

When you have more than one assertion in a test, you can add an identifier for the value being tested, and the identifier becomes part of the failure message.

python
 assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), "chocolate chips")
 assert_that(theBiscuit.getHazelnutCount(), equal_to(3), "hazelnuts")

There is also a convenience form for plain boolean conditions.

python
 assert_that(theBiscuit.isCooked(), "cooked")

The README notes this is equivalent to the assert_ method of unittest.TestCase, but because it is a standalone function it offers greater flexibility in test writing. The practical difference is that the boolean form is not tied to a test case instance, so it works in plain functions, in fixtures and in parameterised tests, which is where a lot of modern Python test suites actually live.

The matcher catalogue, grouped by what they describe

The predefined matchers are listed in the README by category, and the grouping tells you how the library thinks about assertions.

The object group has equal_to, has_length which matches len(), has_property for the value of a named property, has_properties for an object that has all of the given properties, has_string which matches str(), instance_of for the object type, none and not_none, same_instance for object identity, and calling and raises to wrap a method call and assert that it raises.

The number group has close_to for a value within a tolerance, and the ordering matchers greater_than, greater_than_or_equal_to, less_than and less_than_or_equal_to.

The text group has contains_string, ends_with, starts_with, equal_to_ignoring_case, equal_to_ignoring_whitespace, matches_regexp for a regular expression inside a string, and string_contains_in_order for parts appearing in relative order.

The logical group has all_of and any_of to combine matchers, anything to match anything, which is useful inside a composite matcher where you do not care about a particular value, and is_not and not_ to negate.

The sequence group has contains, which matches the entire sequence exactly, contains_inanyorder, has_item, has_items, is_in, only_contains and empty. The dictionary group has has_entries, has_entry, has_key and has_value.

Finally there is a decorator group: calling wraps a callable in a deferred object for subsequent matching on calling behaviour, raises ensures that a deferred callable raises as expected, described_as gives a matcher a custom failure description, and is_ is the readability wrapper described below.

The reason to know this catalogue is that the library is not primarily a set of assertions but a set of vocabulary. A reader who knows that has_length and has_entry exist writes different tests.

Matchers accept matchers, which is where the value is

The README makes one point about composition that justifies the whole design.

The arguments for many of these matchers accept not just a matching value, but another matcher, so matchers can be composed for greater flexibility. The example given is only_contains(less_than(5)), which matches any sequence where every item is less than 5.

Read that carefully, because it is the feature that plain assert statements do not give you. With a plain assert you would write a generator expression and hope the failure message is readable. With composable matchers the constraint is a small object, it can be named, it can be reused, and it can be handed to another matcher.

That is what makes a matcher a first class value rather than a function call. A matcher can be stored in a variable, passed to a helper that builds a predicate, returned from a factory function, or used as the expected value of another assertion.

The repository carries two examples that make this concrete. There is examples/ExampleWithAssertThat.py, and there is examples/CustomDateMatcher.py, which is the more interesting of the two, because it shows how a project writes its own matcher when the built-in ones do not cover the case. A date comparison with a tolerance is the kind of requirement that every project hits and that no general library can anticipate, and the custom matcher pattern is the answer.

is_, and the readability it buys

The syntactic sugar section is short and its point is that the library tries to make tests as readable as possible.

The is_ matcher is a wrapper that adds no behaviour to the underlying matcher, and the README shows three assertions that are all equivalent.

python
 assert_that(theBiscuit, equal_to(myBiscuit))
 assert_that(theBiscuit, is_(equal_to(myBiscuit)))
 assert_that(theBiscuit, is_(myBiscuit))

The third form is allowed because is_(value) wraps most non-matcher arguments with equal_to, so you can write is_ around a plain value and still get the right matcher underneath.

The reason this exists is that equal_to on its own is noisy to read, because it is a function call around the expected value, and in a test with several assertions the eye has to parse every one of them. is_(myBiscuit) reads as a sentence, which is the point of a matcher library in the first place, since the assertion is meant to look like a statement about behaviour rather than a call.

It is also the part of the API that people are most likely to overuse. A test where every assertion is wrapped in is_ loses the distinction between the interesting matcher and the trivial one, and the README's own ordering, which puts the plain equal_to first, suggests the wrapper is for the cases where the matcher is complex enough to need the emphasis.

Modern packaging, and a changelog split across files

The packaging tells you what kind of project this is now.

The build system is hatchling with hatch-vcs, which means the version is derived from the git tags rather than written in the file, and the manifest marks it as dynamic. The project requires Python 3.10 or newer and is tested on CPython, Jython and PyPy, with classifiers for Python 3.10 through 3.14.

The tooling is modern in a way that shows in the tree. There is a tox.ini, a uv.lock, a .pre-commit-config.yaml, a .readthedocs.yml for the documentation, a release.sh script, and a changelog.d/ directory. The changelog.d directory is a towncrier style layout, where each change is a separate file that is assembled into CHANGELOG.rst at release time, which is how a project accepts contributions that only touch the changelog.

The dependency groups are equally deliberate. There is a docs group with sphinx and alabaster, a typing group with mypy and types-mock, and a test group with pytest, coverage, pytest-sugar, pytest-xdist and pyyaml, plus a pytest-mypy-plugins entry, which is a way of type checking the code in the test suite itself. A separate test-numpy group exists so that the numpy dependent tests can be run on their own.

The docs/ directory holds the Sphinx documentation, and the docker/ directory suggests the project is tested in containers as well as locally.

The release history is short. V2.0.2 was published in March 2020, V2.0.3 in December 2021, and V2.1.0 in October 2023, whose release note reads Adding features for async futures. The last push to the repository was on 2026-10-18, so there is work in the tree that is newer than the newest published release.

Where PyHamcrest is the wrong choice

Three situations rule it out.

The first is a small test suite. If you have twenty tests that each assert one thing, plain assert statements are shorter, they need no import, and their failure output in pytest is already good. Introducing a matcher vocabulary for twenty assertions is overhead without benefit, and the failure messages pytest produces for a simple equality check are perfectly readable.

The second is a team that wants pytest's output. PyHamcrest and pytest coexist, but the failure message is the matcher description, not pytest's own diff. A team whose debugging habit depends on that diff, and who works in a codebase where most assertions are simple equalities, will find Hamcrest's messages less informative for the common case and better only for the composite ones.

The third is a project that needs async support beyond what V2.1.0 added. The newest release describes itself as adding features for async futures, and the repository has continued to move since then without a published version, which means the async behaviour you would rely on may only be in the git tree.

There is also a documentation caveat worth flagging. The installation section of the README says the package depends on distribute, which is a relic of a packaging era long gone, and the current manifest has no runtime dependencies at all. A reader who follows the README's install note expects a dependency that will not appear. The rest of the README is in good shape, but that paragraph should be read as historical rather than as instructions.

Editorial conclusion

PyHamcrest fits a test suite that has grown past the point where assertEqual on a whole object is useful, because a matcher names the aspect under test, and a failure message says what was expected rather than printing two large structures. It is the wrong choice for a small suite where plain asserts read perfectly well, and the wrong choice if you want the failures to look like pytest's own output. Before adopting it, note that the newest release is V2.1.0 from 2023-10-22 and that the last push to the repository was on 2026-10-18, so check the changelog directory for work that is not in a published version, and read the README carefully, because its installation note still refers to a dependency the current packaging no longer has.

Frequently asked questions

What problem does PyHamcrest solve in Python tests?

It lets a test state precisely which aspect of a value matters and to what degree of precision, which avoids both overspecifying a test and leaving it too vague to catch a regression. The README describes it as a framework for writing matcher objects that declaratively define match rules.

How do I install PyHamcrest?

Run pip install PyHamcrest. The README's installation note still says the package depends on distribute, which is out of date: the current pyproject.toml declares requires-python of 3.10 or newer, and the package is BSD-3-Clause licensed.

What is the difference between assert_that and assertEqual?

assert_that takes the value under test as the first argument and a matcher as the second, so the assertion reads as a sentence about behaviour, and an optional third argument identifies the value for the failure message. It also works outside a test case instance, which the README notes gives it more flexibility than the unittest assert_ method.

Can PyHamcrest matchers be combined?

Yes. The README states that the arguments for many matchers accept not just a matching value but another matcher, so matchers compose, for example only_contains(less_than(5)) matching any sequence whose items are all less than 5.

What is the latest PyHamcrest release?

V2.1.0, published on 2023-10-22, with the release note Adding features for async futures, after V2.0.3 in December 2021 and V2.0.2 in March 2020. The last push to the repository was on 2026-10-18, and the package is classified as Development Status 5, Production or Stable.

Official sources

  1. hamcrest/PyHamcrest on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hamcrest-pyhamcrest.svg)](https://hysenlabs.com/projects/hamcrest-pyhamcrest)

Was this useful?