|
| 1 | +# Test Mocking Patterns Skill |
| 2 | + |
| 3 | +The `test-mocking-patterns` skill guides test double selection and implementation. It activates when you need to decide which double to use (mock, stub, spy, fake, dummy), implement a double in Python, JavaScript/TypeScript, or Scala, or diagnose a mock that isn't working. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What it does |
| 8 | + |
| 9 | +Given a dependency in the code under test, the skill: |
| 10 | + |
| 11 | +1. **Classifies the dependency** — query (return value matters) vs command (side effect to verify) vs fire-and-forget |
| 12 | +2. **Recommends the right double** — stub, mock, spy, fake, or dummy based on intent |
| 13 | +3. **Provides implementation guidance** — correct patching paths, library calls, and cleanup |
| 14 | +4. **Diagnoses broken mocks** — wrong patch target, missing cleanup, over-specified assertions, wrong return type |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Double selection quick reference |
| 19 | + |
| 20 | +| Dependency interaction | Recommended double | |
| 21 | +|---|---| |
| 22 | +| Returns a value the unit under test uses; no need to verify the call | **Stub** | |
| 23 | +| Called for a side effect; test must verify it was called | **Mock** | |
| 24 | +| Need to verify the call AND preserve the real return value | **Spy** | |
| 25 | +| Stateful in-process replacement of an interface | **Fake** | |
| 26 | +| Required by the type signature but never invoked | **Dummy** | |
| 27 | + |
| 28 | +> Prefer stubs over mocks — stubs make fewer assumptions about internal behaviour, keeping tests less brittle. |
| 29 | +
|
| 30 | +--- |
| 31 | + |
| 32 | +## Languages covered |
| 33 | + |
| 34 | +| Language | Libraries | |
| 35 | +|---|---| |
| 36 | +| Python | `pytest-mock`, `unittest.mock`, `responses`, `pytest-httpx`, `freezegun` | |
| 37 | +| JavaScript / TypeScript | Jest (`jest.fn()`, `jest.mock()`, `jest.spyOn()`) | |
| 38 | +| Scala | mockito-scala | |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## How to trigger it |
| 43 | + |
| 44 | +Ask naturally — the skill fires on intent: |
| 45 | + |
| 46 | +``` |
| 47 | +should I use a mock or stub for the HTTP client? |
| 48 | +what test double should I use for the payment gateway? |
| 49 | +my mock isn't being called — the real implementation runs instead |
| 50 | +where should I patch requests.get? |
| 51 | +how do I mock an environment variable? |
| 52 | +how do I verify a method was called with specific arguments? |
| 53 | +should I mock the boto3 client directly or wrap it? |
| 54 | +how do I freeze time in a pytest test? |
| 55 | +``` |
| 56 | + |
| 57 | +> **Does NOT trigger** for writing full test suites (use `test-unit-write`), reviewing test files for standards violations (use `test-unit-review`), or managing test data and fixtures (use `test-data-management`). |
| 58 | +
|
| 59 | +--- |
| 60 | + |
| 61 | +## Common patching mistakes |
| 62 | + |
| 63 | +| Symptom | Likely cause | Fix | |
| 64 | +|---|---|---| |
| 65 | +| Mock is never called; real code runs | Mock not injected, or wrong patch target | Confirm the unit receives the mock; patch where the name is imported | |
| 66 | +| `assert_called` fails but real call visible in logs | Patching the source module, not the import location | Patch `mymodule.requests.get`, not `requests.get` | |
| 67 | +| Mock state bleeds between tests | No cleanup | Use `mocker` fixture (Python) or `jest.clearAllMocks()` in `beforeEach` | |
| 68 | +| Test breaks on unrelated internal changes | Over-specified assertions | Use `ANY` / `expect.any()` for irrelevant args | |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +## Key principles |
| 73 | + |
| 74 | +### Patch where the name is imported (Python) |
| 75 | + |
| 76 | +```python |
| 77 | +# Module under test: import requests as http_lib |
| 78 | +# ✅ — patch the name in the module under test |
| 79 | +mocker.patch("myapp.service.http_lib.get", return_value=stub_response) |
| 80 | + |
| 81 | +# ❌ — patching the source has no effect on the already-imported alias |
| 82 | +mocker.patch("requests.get", return_value=stub_response) |
| 83 | +``` |
| 84 | + |
| 85 | +### Don't mock what you don't own |
| 86 | + |
| 87 | +Avoid mocking third-party types (boto3, SQLAlchemy sessions, gRPC stubs) directly. Wrap them in a thin interface you control and mock that interface instead. The integration test verifying the real connector belongs in an integration test, not a unit test. |
| 88 | + |
| 89 | +### Use freezegun for datetime (Python) |
| 90 | + |
| 91 | +`datetime` is a C extension — patching it directly is fragile. Use `freezegun`: |
| 92 | + |
| 93 | +```python |
| 94 | +from freezegun import freeze_time |
| 95 | + |
| 96 | +@freeze_time("2025-01-15 12:00:00") |
| 97 | +def test_timestamp_is_fixed(): |
| 98 | + ... |
| 99 | +``` |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Installation |
| 104 | + |
| 105 | +The skill is installed with the rest of the toolkit: |
| 106 | + |
| 107 | +```bash |
| 108 | +npx skills add https://github.com/AbsaOSS/agentic-toolkit -g |
| 109 | +``` |
| 110 | + |
| 111 | +To install only this skill: |
| 112 | + |
| 113 | +```bash |
| 114 | +npx skills add https://github.com/AbsaOSS/agentic-toolkit -g --skill test-mocking-patterns |
| 115 | +``` |
0 commit comments