Make unittest assertions act as type guards

In the following unit test, I get a typing error (using mypy or PyLance), because the type checker believes that ret might be an int, even though it has been asserted in the line before that it actually isn’t:

class TypeTest(unittest.TestCase):

    def fut(self) -> list | int:
        return [42]

    def test_type(self):
        ret = self.fut()
        self.assertIsInstance(ret, list)
        self.assertEqual(ret[0], 42)  # typing error: not indexable

If I change the line self.assertIsInstance(value, list) to assert isinstance(value, list) then there is no typing error and everything works as expected.

The explanation is probably that the assert statement acts as a type guard, while the method self.assertIsInstance provided by the unittest module doesn’t.

A similar issue is this:

class TypeTest(unittest.TestCase):

    def fut(self) -> list | None:
        return [42]

    def test_type(self):
        ret = self.fut()
        self.assertIsNotNone(ret , 42)
        self.assertEqual(ret[0], 42)  # typing error: not indexable

Again, if I change the line self.assertIsNotNone(value, 42) to assert ret is not None then everything is fine.

My suggestion is to make these assertions in the unittest module act as type guards, similar to the assert statement.

1 Like

Type narrowing for TypeGuard in the negative case · python/typing · Discussion #1013 · GitHub is closest relevant discussion. TypeGuard from pep 647 exists and can almost do this, but currently does not support a way for assertion like type guards. That discussion topic includes extensions of TypeGuard to cover this case and there’s open PEP pr, PEP 724: Stricter TypeGuard by rchiodo · Pull Request #3266 · python/peps · GitHub that would extend TypeGuard. Unsure if current pr handles this case or not. I think current pr doesn’t include TypeAssert functionality needed but good to confirm.

I remember these being discussed on typing-sig a few years ago. Here specifically is a discussion of TypeAssert and StrictTypeAssert.

With those you could presumably write:

def assertIsInstance(value: object, typ: Type[T]) -> StrictTypeAssert[T]: ...
def assertTrue(value: object) -> StrictTypeAssert[Literal[True]]: ...
def assertIsNone(value: object) -> StrictTypeAssert[None]: ...
1 Like

Thanks for your feedback @mdrissi and @davidfstr. Your’re right, the existing TypeGuard canot be used to implement this, we would need a TypeAssert or maybe a StrictTypeAssert. Maybe this is a good use case for extending PEP 647.