How to achieve assertDictEqual with assertSequenceEqual applied to values
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.
Related videos on Youtube
sapi
Updated on July 09, 2022Comments
-
sapi almost 2 years
I know that, when performing
assertEqual
on a dictionary,assertDictEqual
is called. Similarly,assertEqual
on a sequence will performassertSequenceEqual
.However, when
assertDictEqual
is comparing values, it appears not to make use ofassertEqual
, and thusassertSequenceEqual
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
andd2
such that their equality is properly compared, by recursively applyingassertEqual
-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 over 10 yearsWith the
unittest
module,self.assertEqual(lst1, lst2)
isn't True -->AssertionError: Lists differ: [1, 2] != [2, 1]
. -
sapi over 10 years@martineau - my mistake; I misread that part of the documentation. I am looking for an equivalent of
assertItemsEqual
rather thanassertSequenceEqual
-
martineau over 10 yearsWell, if you make
lst1
andlst2
the same so the firstassertEqual
succeeds, then the second one will, too. -
martineau over 10 yearsThe part about "looking for an equivalent of
assertItemsEqual
rather thanassertSequenceEqual
" makes no sense to me. My point was that the code in your question is usingassertEqual
on two lists with items in a different order and that assertion will fail. -
Quantum7 almost 5 yearsIf you control the code creating the datastructures, maybe you should use sets instead of lists.
-
-
sapi over 10 yearsI 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 over 10 yearsOK, but I still don't understand why/how you expect the first
assertEqual(lst1, lst2)
to be True in your code. -
Federico almost 9 yearsThis solution does not work on deeply nested dicts + lists. The solution based on sorting does.
-
Mouscellaneous almost 8 yearsYou could use set notation to improve this, instead of
set(['a','b'])
try{'a','b'}
. Also, there is a functionself.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 almost 7 years
Exception: '<' not supported between instances of 'dict' and 'dict'
-
Jorge Machado almost 2 yearsDoes not work for none inside of a list