Search code examples
pythondictionarypython-unittestpyhamcrest

pyhamcrest contains matcher for list of dicts


Why pyhamcrest is failing contains() matcher for list of 2 or more dicts, but works fine with list of 1 dict?

PyHamcrest contains

What is the best way to write hamcrest (pyhamcrest) matcher for list of dicts?

First test passed, second failed in this unittest example.

import unittest
from hamcrest import contains, assert_that, has_entries

class ContainsTest(unittest.TestCase):

    dict1, dict2 = {"a": 1, "b": 2}, {"a": 1, "b": 2}
    sequence1, sequence2 = list(), list()
    sequence1.append(dict1)
    sequence2.append(dict1)
    sequence2.append(dict2)

    @staticmethod
    def test_sequence():
        assert_that(ContainsTest.sequence1, contains(has_entries({'a': 1, 'b': 2})))
        assert_that(ContainsTest.sequence2, contains(has_entries({'a': 1, 'b': 2})))


if __name__ == "__main__":  
    ContainsTest.test_sequence()

Unittest output:

File "/usr/local/lib/python3.6/site-packages/hamcrest/core/assert_that.py", 
line 57, in _assert_match raise AssertionError(description) 
AssertionError:  
Expected: a sequence containing [a dictionary containing {'a': <1>, 'b': <2>}]
but: Not matched: <{'a': 1, 'b': 2}>

Ran 1 test in 0.027s
FAILED (failures=1)
Process finished with exit code 1

Solution

  • You're looking for has_item, not contains.

    Somewhat confusingly, PyHamcrest's contains isn't a containment check in the sense of the __contains__ magic method. Its semantics are modeled after the original Hamcrest's contains matcher. It's not testing if some element of the list matches the given matcher; it wants a separate matcher for each list element, and it'll apply matchers to corresponding elements of the list. has_item is the one that checks if some item matches.

    Quoting the docs:

    Matches if sequence’s elements satisfy a given list of matchers, in order.

    Parameters: match1,... – A comma-separated list of matchers.
    This matcher iterates the evaluated sequence and a given list of matchers, seeing if each element satisfies its corresponding matcher.

    Any argument that is not a matcher is implicitly wrapped in an equal_to matcher to check for equality.

    You've provided one matcher for a list of two elements. PyHamcrest wants two matchers. The second dict isn't getting matched.