← All skills

unittest Skill

Unit testingPython

Copy and Paste in your Terminal

npx skills add https://github.com/LambdaTest/agent-skills.git --skill unittest-skill

Advanced patterns

Advanced topics and patterns for experienced users.

Python unittest — Advanced Patterns & Playbook

Test Organization with Suites

import unittest
from unittest.mock import patch, MagicMock, AsyncMock, PropertyMock, call

class TestUserService(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        """Once per class — DB connection, expensive resources."""
        cls.db = DatabaseConnection('test')
        cls.db.migrate()

    @classmethod
    def tearDownClass(cls):
        cls.db.drop_all()
        cls.db.close()

    def setUp(self):
        """Before each test — fresh state."""
        self.service = UserService(self.db)
        self.db.begin_transaction()

    def tearDown(self):
        self.db.rollback()

    def test_create_user(self):
        user = self.service.create("Alice", "alice@test.com")
        self.assertIsNotNone(user.id)
        self.assertEqual(user.name, "Alice")
        self.assertRegex(user.email, r'^[\w.]+@[\w.]+$')

    def test_duplicate_email_raises(self):
        self.service.create("Alice", "alice@test.com")
        with self.assertRaises(ValueError) as ctx:
            self.service.create("Bob", "alice@test.com")
        self.assertIn("already exists", str(ctx.exception))

Advanced Mocking

class TestAPIClient(unittest.TestCase):
    @patch('myapp.client.requests.Session')
    def test_retries_on_failure(self, MockSession):
        session = MockSession.return_value
        session.get.side_effect = [
            ConnectionError("timeout"),
            ConnectionError("timeout"),
            MagicMock(status_code=200, json=lambda: {"ok": True})
        ]
        client = APIClient(retries=3)
        result = client.fetch("/data")
        self.assertEqual(session.get.call_count, 3)
        self.assertTrue(result["ok"])

    @patch.multiple('myapp.config', API_URL='http://test', API_KEY='fake-key')
    def test_uses_config(self):
        client = APIClient()
        self.assertEqual(client.base_url, 'http://test')

    def test_context_manager_mock(self):
        mock_file = MagicMock()
        mock_file.__enter__ = MagicMock(return_value=mock_file)
        mock_file.__exit__ = MagicMock(return_value=False)
        mock_file.read.return_value = '{"key": "value"}'
        with patch('builtins.open', return_value=mock_file):
            result = load_config('config.json')
            self.assertEqual(result['key'], 'value')

    @patch.object(UserService, 'validate', return_value=True)
    @patch.object(UserService, 'save')
    def test_chained_patches(self, mock_save, mock_validate):
        mock_save.return_value = User(id=1, name="Alice")
        user = UserService().create_user("Alice")
        mock_validate.assert_called_once()
        mock_save.assert_called_once()

Subtests for Data-Driven Testing

class TestValidator(unittest.TestCase):
    def test_email_validation(self):
        cases = [
            ("user@example.com", True),
            ("invalid", False),
            ("@missing.com", False),
            ("user@.com", False),
            ("a@b.c", True),
        ]
        for email, expected in cases:
            with self.subTest(email=email):
                self.assertEqual(validate_email(email), expected)

Async Testing (Python 3.11+)

class TestAsyncService(unittest.IsolatedAsyncioTestCase):
    async def asyncSetUp(self):
        self.client = AsyncClient()
        await self.client.connect()

    async def asyncTearDown(self):
        await self.client.close()

    async def test_fetch_data(self):
        result = await self.client.fetch("/api/data")
        self.assertEqual(result["status"], "ok")

    async def test_concurrent_operations(self):
        import asyncio
        results = await asyncio.gather(
            self.client.fetch("/a"), self.client.fetch("/b")
        )
        self.assertTrue(all(r["status"] == "ok" for r in results))

Custom Test Runner & Discovery

# Custom runner with XML output
import xmlrunner

if __name__ == '__main__':
    unittest.main(
        testRunner=xmlrunner.XMLTestRunner(output='reports'),
        verbosity=2,
        failfast=True
    )

# Custom discovery
loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.discover('tests', pattern='test_*.py'))
suite.addTests(loader.loadTestsFromName('tests.integration.test_api'))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)

Anti-Patterns

  • assertEqual(result, True) → use assertTrue(result)
  • ❌ Bare except in tests — let exceptions propagate to test runner
  • ❌ Complex setUp that tests depend on implicitly — make dependencies explicit
  • ❌ Using print() for debugging — use self.assertEqual with descriptive messages
  • ❌ Tests that depend on execution order within a class