Claude-initial-setup mocking-strategies

install
source · Clone the upstream repo
git clone https://github.com/VersoXBT/claude-initial-setup
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/VersoXBT/claude-initial-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/testing/mocking-strategies" ~/.claude/skills/versoxbt-claude-initial-setup-mocking-strategies && rm -rf "$T"
manifest: skills/testing/mocking-strategies/SKILL.md
source content

Mocking Strategies

Mocking isolates the code under test from its dependencies. Done right, mocks make tests fast and focused. Done wrong, they create brittle tests coupled to implementation details.

When to Use

  • User needs to test code that calls external APIs or databases
  • User asks about mocks, stubs, spies, or fakes
  • Tests are slow because they hit real services
  • User wants to test error paths that are hard to trigger naturally
  • Tests are brittle and break on internal refactors

Core Patterns

Mocks vs Stubs vs Spies

TypePurposeVerifies Calls?Returns Data?
StubReplace with canned responseNoYes
SpyObserve calls without replacingYesOriginal
MockReplace + verify calls + canned responseYesYes
// STUB: Replace function, return fixed data
const getUser = vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })

// SPY: Watch real function, keep original behavior
const spy = vi.spyOn(userService, 'getUser')
// Real getUser still runs, but calls are recorded

// MOCK: Replace function, return fixed data, verify calls
const sendEmail = vi.fn().mockResolvedValue({ sent: true })
await notifyUser(1)
expect(sendEmail).toHaveBeenCalledWith('alice@example.com', expect.any(String))

Vitest / Jest Mocking

Module mock:

import { describe, it, expect, vi } from 'vitest'
import { processOrder } from './orders'

// Mock the entire payment module
vi.mock('./payment', () => ({
  chargeCard: vi.fn().mockResolvedValue({ success: true, txId: 'tx-123' }),
}))

import { chargeCard } from './payment'

describe('processOrder', () => {
  it('charges the card and returns order confirmation', async () => {
    const order = await processOrder({ userId: 1, amount: 99 })

    expect(chargeCard).toHaveBeenCalledWith({ userId: 1, amount: 99 })
    expect(order.status).toBe('confirmed')
    expect(order.transactionId).toBe('tx-123')
  })

  it('handles payment failure', async () => {
    vi.mocked(chargeCard).mockRejectedValueOnce(new Error('Card declined'))

    await expect(processOrder({ userId: 1, amount: 99 }))
      .rejects.toThrow('Card declined')
  })
})

Spy on object method:

const spy = vi.spyOn(console, 'error').mockImplementation(() => {})

await riskyOperation()

expect(spy).toHaveBeenCalledWith('Operation failed:', expect.any(Error))
spy.mockRestore()

Python unittest.mock

Patching with context manager:

from unittest.mock import patch, MagicMock

def test_send_notification():
    with patch('myapp.services.email.send') as mock_send:
        mock_send.return_value = {"sent": True}

        result = notify_user(user_id=1, message="Hello")

        mock_send.assert_called_once_with(
            to="alice@example.com",
            body="Hello"
        )
        assert result["sent"] is True

Patching with decorator:

@patch('myapp.services.payment.charge_card')
def test_process_order(mock_charge):
    mock_charge.return_value = {"success": True, "tx_id": "tx-123"}

    order = process_order(user_id=1, amount=99)

    mock_charge.assert_called_once_with(user_id=1, amount=99)
    assert order["status"] == "confirmed"

Mock side effects for error testing:

@patch('myapp.services.payment.charge_card')
def test_payment_failure(mock_charge):
    mock_charge.side_effect = PaymentError("Card declined")

    with pytest.raises(PaymentError, match="Card declined"):
        process_order(user_id=1, amount=99)

Dependency Injection for Testability

Design code to accept dependencies, making them mockable:

// HARD TO TEST: Direct import
import { db } from './database'

export async function getUser(id: string) {
  return db.query('SELECT * FROM users WHERE id = $1', [id])
}

// EASY TO TEST: Dependency injection
export function createUserService(db: Database) {
  return {
    async getUser(id: string) {
      return db.query('SELECT * FROM users WHERE id = $1', [id])
    },
  }
}

// In tests:
const mockDb = { query: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }) }
const service = createUserService(mockDb)
const user = await service.getUser('1')

When NOT to Mock

Do not mock these — use the real thing:

1. Pure functions (no side effects, deterministic)
   createSlug('Hello World') -> test with real input/output

2. Value objects and data transformations
   formatDate(date) -> test with real dates

3. Your own code under test
   Do not mock the function you are testing

4. Standard library / language built-ins
   Do not mock Array.map, string methods, Math.random (seed instead)

5. Simple internal utilities
   If formatCurrency() is fast and pure, call it for real

Mock only at system boundaries:

  • External HTTP APIs
  • Database queries
  • File system operations
  • Email/SMS services
  • Payment processors
  • Clock/time (use fake timers instead)

Fake Timers

For time-dependent code, use fake timers instead of mocking Date:

import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'

describe('session expiry', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  it('expires session after 30 minutes', () => {
    const session = createSession()
    expect(session.isValid()).toBe(true)

    vi.advanceTimersByTime(30 * 60 * 1000) // 30 minutes

    expect(session.isValid()).toBe(false)
  })
})

Anti-Patterns

  • Mocking everything: If you mock all dependencies, you are testing the mocking framework, not your code. Mock at boundaries; let internal code run for real.
  • Testing mock behavior:
    expect(mock).toHaveBeenCalled()
    without asserting the result tests that you called a mock, not that your code works. Always assert on output or observable behavior.
  • Mocking what you do not own without integration tests: Mocking a third-party API is fine for unit tests, but you also need integration tests that call the real API to catch contract changes.
  • Implementation-coupled mocks: If a refactor (same behavior, different internal calls) breaks your tests, the mocks are too specific. Mock at a higher level.
  • Not restoring mocks: Forgetting
    mockRestore()
    or
    afterEach(() => vi.restoreAllMocks())
    leaks mock state between tests, causing flaky failures.
  • Mocking pure functions: If a function has no side effects and is fast, call it for real. Mocking it adds complexity without value.

Quick Reference

ScenarioStrategy
External API callMock the HTTP client
Database queryMock the repository/query function
Time-dependent logicFake timers (
vi.useFakeTimers()
)
Error paths
mockRejectedValue
/
side_effect
Pure functionsDo NOT mock — test with real input
Internal utilitiesDo NOT mock — call for real
File I/OMock the fs calls or use temp directory

Mock at boundaries: External services, databases, filesystem, network. Do not mock: Pure functions, value objects, the code under test itself. Always restore:

vi.restoreAllMocks()
in afterEach or use
mockRestore()
. DI for testability: Accept dependencies as parameters, not hard imports.