How to achieve assertDictEqual with assertSequenceEqual applied to values

29,472

Solution 1

Rather than overriding assertDictEqual, why don't you recursively sort your dicts first?

def deep_sort(obj):
    """
    Recursively sort list or dict nested lists
    """

    if isinstance(obj, dict):
        _sorted = {}
        for key in sorted(obj):
            _sorted[key] = deep_sort(obj[key])

    elif isinstance(obj, list):
        new_list = []
        for val in obj:
            new_list.append(deep_sort(val))
        _sorted = sorted(new_list)

    else:
        _sorted = obj

    return _sorted

Then sort, and use normal assertDictEqual:

    dict1 = deep_sort(dict1)
    dict2 = deep_sort(dict2)

    self.assertDictEqual(dict1, dict2)

This approach has the benefit of not caring about how many levels deep your lists are.

Solution 2

The TestCase.assertEqual() method calls the class' assertDictEqual() for dicts, so just override that in your subclass derivation. If you only use other assertXXX methods in the method, the error messages should be almost as nice as the built-in asserts -- but if not you can provide a msg keyword argument when you call them to control what is displayed.

import collections
import unittest

class TestSOquestion(unittest.TestCase):

    def setUp(self):
        pass # whatever...

    def assertDictEqual(self, d1, d2, msg=None): # assertEqual uses for dicts
        for k,v1 in d1.iteritems():
            self.assertIn(k, d2, msg)
            v2 = d2[k]
            if(isinstance(v1, collections.Iterable) and
               not isinstance(v1, basestring)):
                self.assertItemsEqual(v1, v2, msg)
            else:
                self.assertEqual(v1, v2, msg)
        return True

    def test_stuff(self):
        lst1 = [1, 2]
        lst2 = [2, 1]

        d1 = {'key': lst1}
        d2 = {'key': lst2}

        self.assertItemsEqual(lst1, lst2) # True
        self.assertEqual(d1, d2) # True

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

Output:

> python unittest_test.py
.
---------------------------------------------------------------------->
Ran 1 test in 0.000s

OK

>

Solution 3

I had the same problem, i had to test if the fields of a model where correct. And MyModel._meta.get_all_field_names() sometimes returns ['a','b'] and sometimes ['b','a'].

When i run:

self.assertEqual(MyModel._meta.get_all_field_names(), ['a', 'b'])

it sometimes fails.

I solved it by putting both values in a set():

self.assertEqual(set(MyModel._meta.get_all_field_names()), set(['a', 'b'])) #true

self.assertEqual(set(MyModel._meta.get_all_field_names()), set(['b', 'a'])) #true

This will not work (returns True) with:

self.assertEqual(set(['a','a','b','a']), set(['a','b']))  # Also true 

But since i'm checking for field names of a model , and those are unique, this is good by me.

Share:
29,472

Related videos on Youtube

sapi
Author by

sapi

Updated on July 09, 2022

Comments

  • sapi
    sapi almost 2 years

    I know that, when performing assertEqual on a dictionary, assertDictEqual is called. Similarly, assertEqual on a sequence will perform assertSequenceEqual.

    However, when assertDictEqual is comparing values, it appears not to make use of assertEqual, and thus assertSequenceEqual is not called.

    Consider the following simple dictionaries:

    lst1 = [1, 2]
    lst2 = [2, 1]
    
    d1 = {'key': lst1}
    d2 = {'key': lst2}
    
    self.assertEqual(lst1, lst2) # True
    self.assertEqual(d1, d2) # False ><
    

    How can I test dictionaries such as d1 and d2 such that their equality is properly compared, by recursively applying assertEqual-like semantics to values?

    I want to avoid using external modules (as suggested in this question) if at all possible, unless they are native django extensions.


    EDIT

    Essentially, what I am after is a built in version of this:

    def assertDictEqualUnorderedValues(self, d1, d2):
        for k,v1 in d1.iteritems():
            if k not in d2:
                self.fail('Key %s missing in %s'%(k, d2))
    
            v2 = d2[k]
    
            if isinstance(v1, Collections.iterable) and not isinstance(v1, basestring):
                self.assertValuesEqual(v1, v2)
            else:
                self.assertEqual(v1, v2)
    

    The problem with the above code is that the error messages are not as nice as the builtin asserts, and there's probably edge cases I've ignored (as I just wrote that off the top of my head).

    • martineau
      martineau over 10 years
      With the unittest module, self.assertEqual(lst1, lst2) isn't True --> AssertionError: Lists differ: [1, 2] != [2, 1].
    • sapi
      sapi over 10 years
      @martineau - my mistake; I misread that part of the documentation. I am looking for an equivalent of assertItemsEqual rather than assertSequenceEqual
    • martineau
      martineau over 10 years
      Well, if you make lst1 and lst2 the same so the first assertEqual succeeds, then the second one will, too.
    • martineau
      martineau over 10 years
      The part about "looking for an equivalent of assertItemsEqual rather than assertSequenceEqual" makes no sense to me. My point was that the code in your question is using assertEqual on two lists with items in a different order and that assertion will fail.
    • Quantum7
      Quantum7 almost 5 years
      If you control the code creating the datastructures, maybe you should use sets instead of lists.
  • sapi
    sapi over 10 years
    I cannot guarantee the order of the lists. The tests are for a django framework, and I can't rely on the order of database queries being the same between the test set and the expected result. All I care about is that the API is giving me the correct values in some order.
  • martineau
    martineau over 10 years
    OK, but I still don't understand why/how you expect the first assertEqual(lst1, lst2) to be True in your code.
  • Federico
    Federico almost 9 years
    This solution does not work on deeply nested dicts + lists. The solution based on sorting does.
  • Mouscellaneous
    Mouscellaneous almost 8 years
    You could use set notation to improve this, instead of set(['a','b']) try {'a','b'}. Also, there is a function self.assertSetEqual({'a','a','b','a'}, {'a','b'}) which will check the difference between the two sets and fail if they do not contain exactly the same elements.
  • tdc
    tdc almost 7 years
    Exception: '<' not supported between instances of 'dict' and 'dict'
  • Jorge Machado
    Jorge Machado almost 2 years
    Does not work for none inside of a list