5

I ran into an issue when using unittest.TestCase.assertItemsEqual (or assertCountEqual in Python 3) that confused me for a bit, and I wasn't able to find a solution on here, so I'm posting my fix here for posterity:

The following unit test fails under both Python 2 and 3:

import six
import unittest

class Foo(object):
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b
    def __repr__(self):
        return '({},{})'.format(self.a, self.b)
    def __eq__(self, other):
        return self.a == other.a and self.b == other.b
    def __lt__(self, other):
        return (self.a, self.b) < (other.a, other.b)
    __hash__ = None

class Test(unittest.TestCase):
    def test_foo_eq(self):
        self.assertEqual(sorted([Foo()]), sorted([Foo()]))
        six.assertCountEqual(self, [Foo()], [Foo()])
        six.assertCountEqual(self, [Foo(1,2), Foo(2,3)], [Foo(2,3), Foo(1,2)])

unittest.main()

The error message looks like:

======================================================================
ERROR: test_foo_eq (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tsanders/scripts/one_offs/test_unittest_assert_items_equal2.py", line 20, in test_foo_eq
    six.assertCountEqual(self, [Foo(1,2), Foo(2,3)], [Foo(2,3), Foo(1,2)])
  File "/home/tsanders/.local/lib/python2.7/site-packages/six.py", line 673, in assertCountEqual
    return getattr(self, _assertCountEqual)(*args, **kwargs)
  File "/usr/lib64/python2.7/unittest/case.py", line 929, in assertItemsEqual
    differences = _count_diff_all_purpose(first_seq, second_seq)
  File "/usr/lib64/python2.7/unittest/util.py", line 116, in _count_diff_all_purpose
    if other_elem == elem:
  File "/home/tsanders/scripts/one_offs/test_unittest_assert_items_equal2.py", line 11, in __eq__
    return self.a == other.a and self.b == other.b
AttributeError: 'object' object has no attribute 'a'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

1 Answer 1

7

I had to look at the source for the unittest module to solve this.

When the elements of the lists being compared are not hashable, the assertItemsEqual/assertCountEqual function falls back to a different algorithm to compare the lists. That algorithm uses an empty object() as a sentinel, which isn't equality-comparible to an object of type Foo.

The fix was to modify my __eq__ function as follows:

def __eq__(self, other):
    try:
        return self.a == other.a and self.b == other.b
    except AttributeError:
        return False
Sign up to request clarification or add additional context in comments.

1 Comment

This is a common error when writing comparison functions: assuming that other is also an instance of your class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.