Currently we have 4 different APIs to work with Exceptions and ExceptionGroups:
with self.assertRaises(E)
self.assertRaises(E, callable)
with self.assertRaisesRegex(E, msg)
self.assertRaisesRegex(E, msg, callable)
This code works as expected:
class ExceptionGroupTests(unittest.TestCase):
def test_exception_group(self):
with self.assertRaises(ExceptionGroup) as exc:
raise ExceptionGroup('A', [TypeError('message1')])
self.assertEqual(len(exc.exception.exceptions), 1)
def raises():
raise ExceptionGroup('A', [TypeError('message2')])
self.assertRaises(ExceptionGroup, raises)
It works as expected, I don’t think that we need to change / add anything here.
One can also check all internals of exc.exception to use native ExceptionGroup APIs like .split, etc.
But, we don’t have a way to make a clean assert that TypeError('message1') (for example) is raised as a part of the group. Now we would have to write something like:
self.assertEqual(
len(exc.exception.subgroup(
lambda exc: (
isinstance(exc, TypeError)
and exc.args[0] == 'message1'
)
).exceptions),
1,
)
which is not really great in terms of usability.
Proposed solution
pytest already has an API that we are looking for How to write and report assertions in tests - pytest documentation
Source: https://github.com/pytest-dev/pytest/blob/c69156e4a1206b974cbda95798ef445039824a99/src/\_pytest/raises.py#L733-L1457
There are several use-cases that we want to cover:
- Asserting that
raise CustomExceptionGroup('my message', [sublist]) from None was raised with give entries in sublist
- Asserting that
sublist contains of specific types with specific messages / patterns
- Optionally flattening nested subgroups
- Having an existing
ExceptionGroup object, it would be nice to be able to check whether or not some sub-exception matches. Like pytest does with RaisesGroup(ExceptionGroup('a', [TypeError('b')])).matches(TypeError('b')) will return True
My proposal is to add:
.assertRaisesGroup and .assertRaisesGroupRegex methods that will behave very similarly to pytest’s RaisesGegex
Examples:
# Asserting custom exception group type:
with self.assertRaisesGroup(TypeError, ValueError, msg=r'group \w+', group=CustomExceptionGroup):
...
# Asserting flattened exception group:
with self.assertRaisesGroup(TypeError, ValueError, flatten=True):
...
# Post-processing exception group after successful raise:
with self.assertRaisesGroup(TypeError, ValueError) as cm:
...
self.assertEqual(len(cm.exception.exceptions), 2)
- Add
RaisedExc helper to be able to call self.assertRaisesGroup with specific sub-exception with specific messages:
with self.assertRaisesGroup(unittest.RaisedExc(TypeError, msg=r'Got \d+ values')) as cm:
...
Basically, all pytest’s features, except:
check= param, because we can post-process exceptions and assert any things we want
allow_unwrapped= param, because why would we want to mix things up?