I had a similar problem to yours, but in my case I was testing the data structures delivered to an API endpoint. What I ended up doing was comparing a standardised JSON serialisation of each of the values. It's not a general solution since it will throw an exception on any value that can't be serialised to JSON, but in my case that's a feature rather than a bug and it does work in your example case, so I thought I'd share.
I created a tests/assertions.py file containing this code:
import json
class ApiAssertionsMixin(object):
def assertJsonEqual(self, first, second, msg=None):
j1 = json.dumps(first, sort_keys=True, indent=4)
j2 = json.dumps(second, sort_keys=True, indent=4)
self.maxDiff = None
self.assertEqual(j1, j2, msg)
In your example, you'd use it like this:
import unittest
from tests.assertions import ApiAssertionsMixin
class DeepCompareTestCase(ApiAssertionsMixin, unittest.TestCase):
def test_compare(self):
self.assertJsonEqual(
[['abc', 'def']],
(('abc', 'def'),)
)
unittest.main()
Which should pass. Here's an example of a failing test:
def test_deep_compare(self):
self.assertJsonEqual(
{ 'name': 'Bob', 'aliases': ['Kate', 'Robbie'], 'age': 19 },
{ 'name': 'Bob', 'age': 20, 'aliases': ['Robbie'] }
)
With output like this:
.F
======================================================================
FAIL: test_deep_compare (__main__.DeepCompareTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests/test_nested.py", line 18, in test_deep_compare
{ 'name': 'Bob', 'age': 20, 'aliases': ['Robbie'] }
File "./tests/assertions.py", line 10, in assertJsonEqual
self.assertEqual(j1, j2, msg)
AssertionError: '{\n "age": 19,\n "aliases": [\n "Kate",\n [41 chars]"\n}' != '{\n "age": 20,\n "aliases": [\n "Robbie"\n[24 chars]"\n}'
{
- "age": 19,
? ^^
+ "age": 20,
? ^^
"aliases": [
- "Kate",
"Robbie"
],
"name": "Bob"
}
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
As you can see, in the event of a failure, you get plenty of context to work out what went wrong.
One downside of this approach is that the output from the test is JSON rather than Python, so if you copy/paste from the output to fix a test, you'll need to translate: true => True, false => False, null => None.