16

While writing some tests for my class, I encountered interesting simple problem. I would like to assertDictEqual two dictionaries containing some list. But this lists may not be sorted in a same way -> which results in failed test

Example:

def test_myobject_export_into_dictionary(self):
    obj = MyObject()
    resulting_dictionary = {
            'state': 2347,
            'neighbours': [1,2,3]
        }
    self.assertDictEqual(resulting_dictionary, obj.exportToDict())

This fail from time to time, depending on order of elements in list

FAIL: test_myobject_export_into_dictionary
------------------------------------
-  'neighbours': [1,2,3],
+  'neighbours': [1,3,2],

Any ideas how to assert this in a simple way?

I was thinking about using set instead of list or sorting lists before comparison.

sjudǝʊ
  • 1,565
  • 1
  • 23
  • 34
  • If you're having many instances of this problem, I'd recommend checking out [@Jon Reid](http://stackoverflow.com/a/14493005/881224)'s answer. – yurisich Jan 24 '13 at 02:42

4 Answers4

8

You might try PyHamcrest (Example corrected)

assert_that(obj.exportToDict(), has_entries(
                                    { 'state': 2347,
                                      'neighbours': contains_inanyorder(1,2,3) }))

(The first value 2347 actually gets wrapped in an implicit equal_to matcher.)

Jon Reid
  • 20,545
  • 2
  • 64
  • 95
  • At first I was like "a library for loose collection matching?"...then I realized that it *does* seem to have a few uses. Nice link. – yurisich Jan 24 '13 at 02:41
  • This looks cool. I like that sentence stylized test assertions. I'll definitely use this framework for testing. However, in my case, it's probably simpler to use sets instead of lists. If I'm asserting bigger dictionary with several lists in it, it starts to be a lot of writing for one assertion. – sjudǝʊ Jan 24 '13 at 11:06
  • @sjudǝʊ Then I'd probably alter the test to use `has_entry` instead of `has_entries` and test each entry separately. – Jon Reid Jan 24 '13 at 16:18
2

You can do:

a = {i:sorted(j) if isinstance(j, list) else j for i,j in resulting_dictionary.iteritems()}
b = {i:sorted(j) if isinstance(j, list) else j for i,j in obj.exportToDict().iteritems()}
self.assertDictEqual(a, b)
thikonom
  • 4,219
  • 3
  • 26
  • 30
  • Of course, that assumes all values can and should be sorted. The first isn't even true in OP's small example, and the latter is to be expected IMHO -- it's rare that list order doesn't matter. –  Jan 23 '13 at 23:29
  • i thought he meant the values will always be lists. – thikonom Jan 23 '13 at 23:30
  • The value for the key `neighbours`, yes. But your code tries to sort all other values too (in OP's example, the value for the key `state`, which is an integer). –  Jan 23 '13 at 23:31
0

How about using all:

assert all( (k,v) in resulting_dictionary.iteritems() 
            for (k,v) in obj.exportToDict().iteritems() )

I use something like this with py.test, but I think it should work for you.


A commenter pointed out that the order will screw me here---fair enough...I'd just use sets, then.

BenDundee
  • 4,389
  • 3
  • 28
  • 34
0

maybe you can check for the two elements separately:

obj_dict = obj.exportToDict()
self.assertEqual(resulting_dictionary['state'], obj_dict['state'])
self.assertCountEqual(resulting_dictionary['neighbours'], obj_dict['neighbours'])
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Guillaume Vincent
  • 13,355
  • 13
  • 76
  • 103