PyHamcrest Documentation

repository·main·Indexed 21 days ago

https://github.com/hamcrest/pyhamcrest

A 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.

Tokens
10.4K
Snippets
38
Records
69
Agent score
73%

What's inside PyHamcrest

  1. Use the is_ matcher for syntactic sugar

    main

    The 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's is operator. For object identity, use same_instance.

    Equivalent forms:

    • assert_that(obj, equal_to(val))
    • assert_that(obj, is_(equal_to(val)))
    • assert_that(obj, is_(val)) (where is_(val) wraps non-matcher arguments with equal_to or instance_of if 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))
  2. Use decorator matchers for better expression

    main

    Decorator 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:

    1. described_as(matcher, description): Wraps a matcher and provides a custom description string to be used in failure messages.
    2. 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)))).
  3. Use is_() for syntactic sugar

    main

    The 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 with equal_to(value).
    • is_(Type): Automatically wraps a type with instance_of(Type).

    Note: is_() is not the same as Python's is operator. To check for object identity, use same_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))
  4. Use logical matchers for boolean logic

    main

    PyHamcrest 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).
  5. Use sequence matchers for collection validation

    main
    PyHamcrest 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.
  6. Test PyHamcrest using Docker

    main

    If you have Docker installed, you can run the PyHamcrest test suite across all supported Python versions using a pre-built Docker image.

    1. Navigate to the directory containing the tox.ini file.
    2. Execute the docker run command provided below.
    docker run --rm -v $(pwd):/src chrisr/pybuilder:latest
  7. Write custom matchers by extending BaseMatcher

    main

    To create a custom matcher, inherit from hamcrest.core.base_matcher.BaseMatcher and implement two methods:

    1. _matches(self, item): Returns True if the item satisfies the matching rule, otherwise False.
    2. describe_to(self, description): Used to build the failure message when the assertion fails. Use the description object 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()))
  8. Modify the PyHamcrest Docker build image

    main

    To customize the environment used for testing PyHamcrest, you can modify the local Dockerfile and build your own image.

    1. Modify the Dockerfile in the current directory to include your desired setup.
    2. Build the image using a tag of your choice: docker build --tag $USER/pybuilder:latest .
    3. Run your build using the new image.

    If you want your changes to be available to others, submit a pull request to the hamcrest/PyHamcrest repository. Once merged, the changes will be published to the chrisr/pybuilder:latest tag.

    docker build --tag $USER/pybuilder:latest .
  9. Assert exceptions from async methods

    main

    When testing asynchronous code, you must handle the Future object returned by the async method. Use resolved to wait for the future to complete and future_raising to assert that the resolved result is an exception.

    This pattern is best used with an async test runner like unittest.IsolatedAsyncioTestCase or pytest-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))
  10. How to implement custom matchers using BaseMatcher

    main

    When creating a custom matcher in PyHamcrest, you should inherit from BaseMatcher.

    For most implementations, you only need to override the _matches(self, item: T) -> bool method. The base class handles the logic for generating mismatch descriptions automatically via the matches method.

    However, if you need to generate a more complex or dynamic mismatch description during the matching process itself, you should override the matches method 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"