passes when the provided exc matches the structure of template individual exceptions don t have to be the same objects or even pass an equality test they only need to be the same type and contain equal excobj args passes when the provided exc matches the structure of template individual exceptions don t have to be the same objects or even pass an equality test they only need to be the same type and contain equal exc_obj args
class ExceptionIsLikeMixin:
    def assertExceptionIsLike(self, exc, template):
        
        if exc is None and template is None:
            return

        if template is None:
            self.fail(f"unexpected exception: {exc}")

        if exc is None:
            self.fail(f"expected an exception like {template!r}, got None")

        if not isinstance(exc, ExceptionGroup):
            self.assertEqual(exc.__class__, template.__class__)
            self.assertEqual(exc.args[0], template.args[0])
        else:
            self.assertEqual(exc.message, template.message)
            self.assertEqual(len(exc.exceptions), len(template.exceptions))
            for e, t in zip(exc.exceptions, template.exceptions):
                self.assertExceptionIsLike(e, t)