๐Ÿ  VisualStudioTutor.com  ยท  Python Tutorial Home  ยท  Python Lesson 17 of 40
Lesson 17 of 40 Testing Intermediate โฑ 35 min

Testing with pytest & unittest.mock

Write professional tests with pytest fixtures, parametrize, monkeypatching, unittest.mock, pytest-asyncio, and coverage reports.

Part 1: What You Will Learn

  • Write focused tests with pytest.
  • Share setup using fixtures.
  • Run several input cases with @pytest.mark.parametrize.
  • Replace external dependencies with unittest.mock.

Part 2: Key Concepts

A good unit test checks one small behaviour and remains fast, deterministic, and independent from external systems. pytest reduces test boilerplate, while unittest.mock lets you replace collaborators such as email senders, API clients, clocks, or repositories.

Part 3: Topic-Specific Code Examples

def calculate_discount(total: float, member: bool) -> float:
    if total < 0:
        raise ValueError("total cannot be negative")
    rate = 0.10 if member else 0.0
    return round(total * (1 - rate), 2)


def checkout(total: float, member: bool, notifier) -> float:
    final_total = calculate_discount(total, member)
    notifier.send(f"Payment due: RM{final_total:.2f}")
    return final_total
import pytest
from unittest.mock import Mock
from app import calculate_discount, checkout

@pytest.fixture
def notifier() -> Mock:
    return Mock()

@pytest.mark.parametrize(
    ("total", "member", "expected"),
    [
        (100.0, False, 100.0),
        (100.0, True, 90.0),
        (250.0, True, 225.0),
    ],
)
def test_calculate_discount(total, member, expected):
    assert calculate_discount(total, member) == expected


def test_negative_total_is_rejected():
    with pytest.raises(ValueError):
        calculate_discount(-1, True)


def test_checkout_sends_notification(notifier):
    result = checkout(100.0, True, notifier)
    assert result == 90.0
    notifier.send.assert_called_once_with("Payment due: RM90.00")
python -m pip install pytest pytest-cov
pytest -q
pytest --cov=app --cov-report=term-missing

Part 4: How the Example Works

The parameterised test reuses one test function for three input combinations. pytest.raises() verifies the error path. The mock notifier records how it was used, so the test can confirm that checkout() sends exactly one expected message without contacting a real service.

Part 5: Hands-On Practice

Mini project โ€” Test a Grade Service. Build a grade(mark) function and test boundary values such as 0, 49, 50, 79, 80, and 100. Then mock a repository in a save_result() function and verify that the expected data is saved.

Part 6: Next Steps

Run and modify the examples in Visual Studio 2026, then continue to Lesson 18. Return to Python Tutorial Home to review the complete curriculum.

๐Ÿ“˜ Want the complete guide with projects? Get the book โ†’