How to assert a dict contains another dict without assertDictContainsSubset in python?

Question:

I know assertDictContainsSubset can do this in python 2.7, but for some reason it’s deprecated in python 3.2. So is there any way to assert a dict contains another one without assertDictContainsSubset?

This seems not good:

for item in dic2:
    self.assertIn(item, dic)

any other good way? Thanks

Asked By: JerryCai

||

Answers:

>>> d1 = dict(a=1, b=2, c=3, d=4)
>>> d2 = dict(a=1, b=2)
>>> set(d2.items()).issubset( set(d1.items()) )
True

And the other way around:

>>> set(d1.items()).issubset( set(d2.items()) )
False

Limitation: the dictionary values have to be hashable.

Answered By: John1024

Here is a comparison that works even if you have lists in the dictionaries:

superset = {'a': 1, 'b': 2}
subset = {'a': 1}

common = { key: superset[key] for key in set(superset.keys()).intersection(set(subset.keys())) }

self.assertEquals(common, subset)
Answered By: user1338062

John1024’s solution worked for me. However, in case of a failure it only tells you False instead of showing you which keys are not matching. So, I tried to avoid the deprecated assert method by using other assertion methods that will output helpful failure messages:

    expected = {}
    response_keys = set(response.data.keys())
    for key in input_dict.keys():
        self.assertIn(key, response_keys)
        expected[key] = response.data[key]
    self.assertDictEqual(input_dict, expected)
Answered By: Risadinha

This answers a little broader question than you’re asking but I use this in my test harnesses to see if the container dictionary contains something that looks like the contained dictionary. This checks keys and values. Additionally you can use the keyword 'ANYTHING' to indicate that you don’t care how it matches.

def contains(container, contained):
    '''ensure that `contained` is present somewhere in `container`

    EXAMPLES:

    contains(
        {'a': 3, 'b': 4},
        {'a': 3}
    ) # True

    contains(
        {'a': [3, 4, 5]},
        {'a': 3},
    ) # True

    contains(
        {'a': 4, 'b': {'a':3}},
        {'a': 3}
    ) # True

    contains(
        {'a': 4, 'b': {'a':3, 'c': 5}},
        {'a': 3, 'c': 5}
    ) # True

    # if an `contained` has a list, then every item from that list must be present
    # in the corresponding `container` list
    contains(
        {'a': [{'b':1}, {'b':2}, {'b':3}], 'c':4},
        {'a': [{'b':1},{'b':2}], 'c':4},
    ) # True

    # You can also use the string literal 'ANYTHING' to match anything
        contains(
        {'a': [{'b':3}]},
        {'a': 'ANYTHING'},
    ) # True

    # You can use 'ANYTHING' as a dict key and it indicates to match the corresponding value anywhere
    # below the current point
    contains(
        {'a': [ {'x':1,'b1':{'b2':{'c':'SOMETHING'}}}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True

    contains(
        {'a': [ {'x':1, 'b':'SOMETHING'}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True

    contains(
        {'a': [ {'x':1,'b1':{'b2':{'c':'SOMETHING'}}}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True
    '''
    ANYTHING = 'ANYTHING'
    if contained == ANYTHING:
        return True

    if container == contained:
        return True

    if isinstance(container, list):
        if not isinstance(contained, list):
            contained = [contained]
        true_count = 0
        for contained_item in contained:
            for item in container:
                if contains(item, contained_item):
                    true_count += 1
                    break
        if true_count == len(contained):
            return True

    if isinstance(contained, dict) and isinstance(container, dict):
        contained_keys = set(contained.keys())
        if ANYTHING in contained_keys:
            contained_keys.remove(ANYTHING)
            if not contains(container, contained[ANYTHING]):
                return False

        container_keys = set(container.keys())
        if len(contained_keys - container_keys) == 0:
            # then all the contained keys are in this container ~ recursive check
            if all(
                contains(container[key], contained[key])
                for key in contained_keys
            ):
                return True

    # well, we're here, so I guess we didn't find a match yet
    if isinstance(container, dict):
        for value in container.values():
            if contains(value, contained):
                return True

    return False
Answered By: JnBrymn

The big problem with the accepted answer is that it does not work if you have non hashable values in your objects values. The second thing is that you get no useful output – the test passes or fails but doesn’t tell you which field within the object is different.

As such it is easier to simply create a subset dictionary then test that. This way you can use the TestCase.assertDictEquals() method which will give you very useful formatted output in your test runner showing the diff between the actual and the expected.

I think the most pleasing and pythonic way to do this is with a simple dictionary comprehension as such:

from unittest import TestCase


actual = {}
expected = {}

subset = {k:v for k, v in actual.items() if k in expected}
TestCase().assertDictEqual(subset, expected)

NOTE obviously if you are running your test in a method that belongs to a child class that inherits from TestCase (as you almost certainly should be) then it is just self.assertDictEqual(subset, expected)

Answered By: Sam Redway

Although I’m using pytest, I found the following idea in a comment. It worked really great for me, so I thought it could be useful here.

Python 3:

assert dict1.items() <= dict2.items()

Python 2:

assert dict1.viewitems() <= dict2.viewitems()

It works with non-hashable items, but you can’t know exactly which item eventually fails.

Answered By: kepler

In Python 3 and Python 2.7, you can create a set-like “item view” of a dict without copying any data. This allows you can use comparison operators to test for a subset relationship.

In Python 3, this looks like:

# Test if d1 is a sub-dict of d2
d1.items() <= d2.items()

# Get items in d1 not found in d2
difference = d1.items() - d2.items()

In Python 2.7 you can use the viewitems() method in place of items() to achieve the same result.

In Python 2.6 and below, your best bet is to iterate over the keys in the first dict and check for inclusion in the second.

# Test if d1 is a subset of d2
all(k in d2 and d2[k] == d1[k] for k in d1)
Answered By: augurar

You can use assertGreaterEqual or assertLessEqual.

users = {'id': 28027, 'email': '[email protected]', 'created_at': '2005-02-13'}
data = {"email": "[email protected]"}

self.assertGreaterEqual(user.items(), data.items())
self.assertLessEqual(data.items(), user.items())  # Reversed alternative

Be sure to specify .items() or it won’t work.

Answered By: Bedram Tamang