Python
unit-testing
assertRaises
NoneType
error-handling

How to properly use unit-testing's assertRaises with NoneType objects

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

assertRaises is for testing exception behavior, not for checking whether a function returned None. The confusion usually starts when a function receives None as input and the test writer has not yet decided what the function is supposed to do. The correct test depends on the contract: reject None with an exception, or accept it and return None.

Use assertRaises When the Contract Is Exception-Based

If None is invalid input, the function should raise a specific exception and the test should assert that exact behavior.

python
1import unittest
2
3
4def normalize_name(name):
5    if name is None:
6        raise TypeError("name must not be None")
7    return name.strip().lower()
8
9
10class NormalizeNameTests(unittest.TestCase):
11    def test_none_raises(self):
12        with self.assertRaises(TypeError):
13            normalize_name(None)

This is correct because the function contract says None is not allowed.

Use assertIsNone When None Is a Valid Outcome

If the function is supposed to return None in some cases, assertRaises is simply the wrong tool.

python
1import unittest
2
3
4def find_user(user_id):
5    return None
6
7
8class UserTests(unittest.TestCase):
9    def test_missing_user_returns_none(self):
10        self.assertIsNone(find_user(999))

That is a return-value assertion, not an exception assertion.

Prefer the Context Manager Form

unittest supports both callable-style and context-manager-style assertRaises. The context-manager form is usually clearer because the exact failing line is obvious.

python
1import unittest
2
3
4def divide(a, b):
5    return a / b
6
7
8class DivideTests(unittest.TestCase):
9    def test_zero_division(self):
10        with self.assertRaises(ZeroDivisionError):
11            divide(10, 0)

This style also makes it easier to add more setup around the failing call later.

Check the Error Message Only When It Matters

If the type alone is not enough, use assertRaisesRegex.

python
1import unittest
2
3
4def parse_age(value):
5    if value is None:
6        raise ValueError("age is required")
7    return int(value)
8
9
10class ParseAgeTests(unittest.TestCase):
11    def test_missing_age_message(self):
12        with self.assertRaisesRegex(ValueError, "required"):
13            parse_age(None)

Use this when the message is part of the function’s public behavior, not for every tiny wording detail.

Test the Contract, Not a Lucky Failure

A weak test says “something failed.” A useful test says exactly what should happen:

  • 'None input raises TypeError'
  • invalid data raises ValueError
  • missing lookup result returns None

That is why the design question comes first. If you do not know whether the function should raise or return None, settle that before writing the assertion.

Parameterize Invalid Inputs When Helpful

If several invalid values should fail, use subTest to keep the test compact but explicit.

python
1import unittest
2
3
4def parse_port(value):
5    if value is None:
6        raise TypeError("port is required")
7    port = int(value)
8    if port <= 0:
9        raise ValueError("port must be positive")
10    return port
11
12
13class ParsePortTests(unittest.TestCase):
14    def test_invalid_cases(self):
15        for item in [None, 0, -1]:
16            with self.subTest(item=item):
17                with self.assertRaises((TypeError, ValueError)):
18                    parse_port(item)

This keeps multiple edge cases readable without duplicating boilerplate.

Callable Style Still Exists

assertRaises also has an older callable form such as self.assertRaises(TypeError, func, None). It works, but the context-manager form is usually easier to read, especially once setup code or message assertions become part of the test.

That readability benefit matters because test code is documentation too, not just a pass-fail mechanism.

Common Pitfalls

  • Using assertRaises for functions that should simply return None.
  • Catching broad Exception instead of a specific exception type.
  • Putting too much code inside the assertRaises block and testing the wrong line.
  • Writing the test before deciding the function’s intended contract.

Summary

  • Use assertRaises only when the expected behavior is an exception.
  • Use assertIsNone when the expected result is None.
  • Prefer the context-manager form for readability.
  • Assert specific exception types rather than broad failure categories.
  • Decide the function contract first, then choose the matching assertion.

Course illustration
Course illustration

All Rights Reserved.