PyHamcrest Documentation
repository·main·Indexed 21 days ago
https://github.com/hamcrest/pyhamcrestA framework for writing matcher objects that allow for declarative 'match' rules, used to create flexible, readable, and precise unit tests by decoupling assertion logic from test structure. It includes a wide array of matchers for sequences, text, dictionaries, numbers, and objects, as well as logical matchers like all_of and any_of, and the primary assert_that entry point for assertions.
What's inside PyHamcrest
- PyHamcrest provides a suite of matchers designed to inspect the properties, types, and contents of Python objects. These include checking for equality, length, string containment, property existence, and type instances.
Use the is_ matcher for syntactic sugar
mainThe
is_matcher is a decorator used to improve the readability of assertions. It does not change the behavior of the underlying matcher.Note:
is_is unrelated to Python'sisoperator. For object identity, usesame_instance.Equivalent forms:
assert_that(obj, equal_to(val))assert_that(obj, is_(equal_to(val)))assert_that(obj, is_(val))(whereis_(val)wraps non-matcher arguments withequal_toorinstance_ofif the argument is a type).
assert_that(theBiscuit, equal_to(myBiscuit)) assert_that(theBiscuit, is_(equal_to(myBiscuit))) assert_that(theBiscuit, is_(myBiscuit)) # For types: assert_that(theBiscuit, instance_of(Biscuit)) assert_that(theBiscuit, is_(instance_of(Biscuit))) assert_that(theBiscuit, is_(Biscuit))Use decorator matchers for better expression
mainDecorator matchers are used to wrap existing matchers to provide more expressive or readable assertions in your tests. They do not change the underlying matching logic but change how the matcher is described or presented.
Two primary decorator matchers are available:
described_as(matcher, description): Wraps a matcher and provides a custom description string to be used in failure messages.is_(matcher): A syntactic sugar decorator that wraps a matcher to make the assertion read more like a natural English sentence (e.g.,assert_that(x, is_(equal_to(1)))).
Use is_() for syntactic sugar
mainThe
is_()matcher is a decorator that improves readability without changing behavior. It can wrap other matchers or raw values.is_(matcher): Wraps a matcher.is_(value): Automatically wraps a non-matcher value withequal_to(value).is_(Type): Automatically wraps a type withinstance_of(Type).
Note:
is_()is not the same as Python'sisoperator. To check for object identity, usesame_instance().from hamcrest import assert_that, is_, equal_to, instance_of, same_instance # These are all equivalent: assert_that(theBiscuit, equal_to(myBiscuit)) assert_that(theBiscuit, is_(equal_to(myBiscuit))) assert_that(theBiscuit, is_(myBiscuit)) # Type checking with is_: assert_that(theBiscuit, instance_of(Biscuit)) assert_that(theBiscuit, is_(instance_of(Biscuit))) assert_that(theBiscuit, is_(Biscuit)) # For object identity (the actual 'is' operator equivalent): assert_that(obj_a, same_instance(obj_b))Use logical matchers for boolean logic
mainPyHamcrest provides logical matchers that allow you to combine multiple matchers using boolean logic. This enables complex assertions by grouping existing matchers together.
Available logical matchers:
all_of(*matchers): Matches if all provided matchers match the item.any_of(*matchers): Matches if at least one of the provided matchers matches the item.anything(): A wildcard matcher that matches anything.is_not(matcher): Negates the provided matcher (matches if the item does NOT match the provided matcher).
Use sequence matchers for collection validation
mainPyHamcrest provides a suite of matchers specifically designed to validate the contents and order of sequences (like lists or tuples). These matchers allow you to assert whether a collection contains specific elements, whether they appear in a specific order, or if the collection matches a specific set of elements exactly.Test PyHamcrest using Docker
mainIf you have Docker installed, you can run the PyHamcrest test suite across all supported Python versions using a pre-built Docker image.
- Navigate to the directory containing the
tox.inifile. - Execute the
docker runcommand provided below.
docker run --rm -v $(pwd):/src chrisr/pybuilder:latest- Navigate to the directory containing the
Write custom matchers by extending BaseMatcher
mainTo create a custom matcher, inherit from
hamcrest.core.base_matcher.BaseMatcherand implement two methods:_matches(self, item): ReturnsTrueif theitemsatisfies the matching rule, otherwiseFalse.describe_to(self, description): Used to build the failure message when the assertion fails. Use thedescriptionobject to append text.
Best Practice: Matchers should be stateless so that a single instance can be reused across multiple matches.
from hamcrest.core.base_matcher import BaseMatcher from hamcrest.core.helpers.hasmethod import hasmethod class IsGivenDayOfWeek(BaseMatcher): def __init__(self, day): self.day = day # Monday is 0, Sunday is 6 def _matches(self, item): if not hasmethod(item, "weekday"): return False return item.weekday() == self.day def describe_to(self, description): day_as_string = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] description.append_text("calendar date falling on ").append_text(day_as_string[self.day]) # Factory function for easy use def on_a_saturday(): return IsGivenDayOfWeek(5) # Usage in test assert_that(some_date, is_(on_a_saturday()))Modify the PyHamcrest Docker build image
mainTo customize the environment used for testing PyHamcrest, you can modify the local
Dockerfileand build your own image.- Modify the
Dockerfilein the current directory to include your desired setup. - Build the image using a tag of your choice:
docker build --tag $USER/pybuilder:latest . - Run your build using the new image.
If you want your changes to be available to others, submit a pull request to the
hamcrest/PyHamcrestrepository. Once merged, the changes will be published to thechrisr/pybuilder:latesttag.docker build --tag $USER/pybuilder:latest .- Modify the
Install PyHamcrest via pip
mainYou can install PyHamcrest using standard Python packaging tools. It requires a network connection during installation to handle its dependency on
distribute.pip install PyHamcrestAssert exceptions from async methods
mainWhen testing asynchronous code, you must handle the
Futureobject returned by the async method. Useresolvedto wait for the future to complete andfuture_raisingto assert that the resolved result is an exception.This pattern is best used with an async test runner like
unittest.IsolatedAsyncioTestCaseorpytest-asyncio.from hamcrest import assert_that, resolved, future_raising async def parse(input: str): ... class Test(unittest.IsolatedAsyncioTestCase): async def testParse(self): future = parse("some bad data") # Wait for the future and assert it raised a ValueError assert_that(await resolved(future), future_raising(ValueError))How to implement custom matchers using BaseMatcher
mainWhen creating a custom matcher in PyHamcrest, you should inherit from
BaseMatcher.For most implementations, you only need to override the
_matches(self, item: T) -> boolmethod. The base class handles the logic for generating mismatch descriptions automatically via thematchesmethod.However, if you need to generate a more complex or dynamic mismatch description during the matching process itself, you should override the
matchesmethod instead of_matches.from hamcrest.core.base_matcher import BaseMatcher class MyCustomMatcher(BaseMatcher): def _matches(self, item): # Implement your matching logic here return item == "expected_value"