|
8 | 8 | from contextlib import contextmanager
|
9 | 9 | from io import StringIO
|
10 | 10 | from unittest import TestCase
|
| 11 | +from unittest.util import safe_repr |
11 | 12 |
|
12 | 13 | from django.test import Client
|
13 | 14 |
|
@@ -72,6 +73,38 @@ def sample_email_content(filename=SAMPLE_EMAIL_FILENAME):
|
72 | 73 | class AnymailTestMixin(TestCase):
|
73 | 74 | """Helpful additional methods for Anymail tests"""
|
74 | 75 |
|
| 76 | + def assertDictMatches(self, expected, actual, msg=None): |
| 77 | + """ |
| 78 | + Tests that dict `expected` is a subset of `actual`. (That expected |
| 79 | + is *in* actual. Note the args are in the same needle, haystack |
| 80 | + order as assertIn.) |
| 81 | + """ |
| 82 | + # This is just assertDictContainsSubset, and the code is copied in from there. |
| 83 | + # (That was deprecated because apparently the arg order was confusing given the |
| 84 | + # name. The "matches" terminology is borrowed from several JS expect packages.) |
| 85 | + missing = [] |
| 86 | + mismatched = [] |
| 87 | + for key, value in expected.items(): |
| 88 | + if key not in actual: |
| 89 | + missing.append(key) |
| 90 | + elif value != actual[key]: |
| 91 | + mismatched.append('%s, expected: %s, actual: %s' % |
| 92 | + (safe_repr(key), safe_repr(value), |
| 93 | + safe_repr(actual[key]))) |
| 94 | + |
| 95 | + if not (missing or mismatched): |
| 96 | + return |
| 97 | + |
| 98 | + standardMsg = '' |
| 99 | + if missing: |
| 100 | + standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in missing) |
| 101 | + if mismatched: |
| 102 | + if standardMsg: |
| 103 | + standardMsg += '; ' |
| 104 | + standardMsg += 'Mismatched values: %s' % ','.join(mismatched) |
| 105 | + |
| 106 | + self.fail(self._formatMessage(msg, standardMsg)) |
| 107 | + |
75 | 108 | @contextmanager
|
76 | 109 | def assertDoesNotWarn(self, disallowed_warning=Warning):
|
77 | 110 | """Makes test error (rather than fail) if disallowed_warning occurs.
|
|
0 commit comments