diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b015b..82367a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Nothing notable unreleased. tests equality of Mapping objects not requiring them to be dicts. Similar to `assertSequenceEqual` but for mappings. +* (testing) Added a new method `absltest.assertDictContainsSubset` that + checks that a dictionary contains a subset of keys and values. Similar + to a removed method `unittest.assertDictContainsSubset` (existed until Python 3.11). + ### Fixed * (testing) Fixed an issue where the test reporter crashes with exceptions with diff --git a/absl/testing/absltest.py b/absl/testing/absltest.py index 26a033b..3e8ebcf 100644 --- a/absl/testing/absltest.py +++ b/absl/testing/absltest.py @@ -1656,6 +1656,19 @@ def CheckEqual(a, b): for a, b in itertools.product(group, group): CheckEqual(a, b) + def assertDictContainsSubset(self, subset, dictionary, msg=None): + """Raises AssertionError if dictionary is not a superset of subset. + + Args: + subset: A dict, the expected subset of the `dictionary`. + dictionary: A dict, the actual value. + msg: An optional str, the associated message. + + Raises: + AssertionError: if dictionary is not a superset of subset. + """ + self.assertDictEqual(dictionary, {**dictionary, **subset}, msg) + def assertDictEqual(self, a, b, msg=None): """Raises AssertionError if a and b are not equal dictionaries. diff --git a/absl/testing/tests/absltest_test.py b/absl/testing/tests/absltest_test.py index dbaa62a..1f633dc 100644 --- a/absl/testing/tests/absltest_test.py +++ b/absl/testing/tests/absltest_test.py @@ -1597,6 +1597,61 @@ def test_assert_json_equal_bad_json(self): with self.assertRaises(ValueError) as error_context: self.assertJsonEqual('', '') + @parameterized.named_parameters( + dict(testcase_name='empty', subset={}, dictionary={}), + dict(testcase_name='empty_is_a_subset', subset={}, dictionary={'a': 1}), + dict( + testcase_name='equal_one_element', + subset={'a': 1}, + dictionary={'a': 1}, + ), + dict( + testcase_name='subset', subset={'a': 1}, dictionary={'a': 1, 'b': 2} + ), + dict( + testcase_name='equal_many_elements', + subset={'a': 1, 'b': 2}, + dictionary={'a': 1, 'b': 2}, + ), + ) + def test_assert_dict_contains_subset( + self, subset: type[dict], dictionary: type[dict] + ): + self.assertDictContainsSubset(subset, dictionary) + + @parameterized.named_parameters( + dict(testcase_name='subset_vs_empty', subset={1: 'one'}, dictionary={}), + dict( + testcase_name='value_is_different', + subset={'a': 2}, + dictionary={'a': 1}, + ), + dict( + testcase_name='key_is_different', subset={'c': 1}, dictionary={'a': 1} + ), + dict( + testcase_name='subset_is_larger', + subset={'a': 1, 'c': 1}, + dictionary={'a': 1}, + ), + dict( + testcase_name='UnicodeDecodeError_constructing_failure_msg', + subset={'foo': ''.join(chr(i) for i in range(255))}, + dictionary={'foo': '\uFFFD'}, + ), + ) + def test_assert_dict_contains_subset_fails( + self, subset: type[dict], dictionary: type[dict] + ): + with self.assertRaises(self.failureException): + self.assertDictContainsSubset(subset, dictionary) + + def test_assert_dict_contains_subset_fails_with_msg(self): + with self.assertRaisesRegex( + AssertionError, re.compile('custom message', re.DOTALL) + ): + self.assertDictContainsSubset({'a': 1}, {'a': 2}, msg='custom message') + class GetCommandStderrTestCase(absltest.TestCase):