Testing Exceptions, setUp() & tearDown() Fixtures
In enterprise Python codebases, your tests need to do more than assert simple math operations:
- You must verify that your code raises the correct exceptions when given bad data.
- You must create temporary mock data before every test and clean it up immediately after so tests do not interfere with each other.
1. Testing Expected Exceptions with assertRaises()โ
When a function is supposed to raise an error (for example, throwing a ValueError when a bank withdrawal amount is negative), use self.assertRaises() as a context manager:
import unittest
def withdraw_from_account(current_balance, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be strictly greater than 0!")
if amount > current_balance:
raise PermissionError("Insufficient funds available.")
return current_balance - amount
class TestBankingRules(unittest.TestCase):
def test_negative_withdrawal_raises_value_error(self):
# Asserts that ValueError is successfully thrown
with self.assertRaises(ValueError):
withdraw_from_account(500, -100)
def test_overdraft_raises_permission_error(self):
with self.assertRaises(PermissionError):
withdraw_from_account(500, 1000)
2. Test Fixtures: setUp() & tearDown()โ
If you have 10 test methods that all need a fresh SQLite database connection or a temporary mock shopping cart:
setUp(): Runs automatically before every single test method.tearDown(): Runs automatically after every single test method (even if the test fails!).
import unittest
class TestShoppingCart(unittest.TestCase):
def setUp(self):
# Runs BEFORE each test: Create a clean cart with 1 initial product
self.cart = ["Laptop Sleeve"]
print("\n๐ง Setup: Fresh shopping cart created.")
def tearDown(self):
# Runs AFTER each test: Reset cart state
self.cart.clear()
print("๐งน Teardown: Cart cleaned up.")
def test_add_item(self):
self.cart.append("Wireless Mouse")
self.assertEqual(len(self.cart), 2)
def test_cart_has_initial_item(self):
self.assertIn("Laptop Sleeve", self.cart)
self.assertEqual(len(self.cart), 1)
3. Class-Level Fixtures: setUpClass() & tearDownClass()โ
If setting up your resource is heavy and expensive (like creating an in-memory SQLite database table), you don't want to recreate it 50 times.
Use @classmethod setUpClass(cls) to run setup once for the entire test class:
import unittest
import sqlite3
class TestDatabaseQueries(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Runs ONCE before all tests in this class
cls.conn = sqlite3.connect(":memory:")
cls.cursor = cls.conn.cursor()
cls.cursor.execute("CREATE TABLE products (id INT, name TEXT)")
cls.cursor.execute("INSERT INTO products VALUES (1, 'Mechanical Keyboard')")
@classmethod
def tearDownClass(cls):
# Runs ONCE after all tests complete
cls.conn.close()
def test_fetch_product(self):
self.cursor.execute("SELECT name FROM products WHERE id = 1")
item = self.cursor.fetchone()
self.assertEqual(item[0], "Mechanical Keyboard")
Quick Summaryโ
- Testing Exceptions: Use
with self.assertRaises(ExpectedException):to verify invalid input triggers correct errors. - Method Fixtures (
setUp/tearDown): Execute before and after each individual test method to ensure clean isolated test state. - Class Fixtures (
setUpClass/tearDownClass): Run once per test class for expensive shared resources (e.g. database schemas).
What's Next?โ
Let's explore the industry-standard modern testing framework with expressive asserts and powerful parametrization in Introduction to pytest Framework!