بنقرة واحدة
test-mocking-rules
Comprehensive guidelines for when and how to use mocks, stubs, and fakes in tests
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Comprehensive guidelines for when and how to use mocks, stubs, and fakes in tests
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Plan before implementing - understand scope and approach with detailed guidance
Comprehensive guide for implementing code incrementally following established patterns, conventions, and quality standards
Comprehensive checklist for documenting follow-up work and testing needs after implementation
Comprehensive systematic approach to achieving complete test coverage through structured category-based testing
| name | test-mocking-rules |
| description | Comprehensive guidelines for when and how to use mocks, stubs, and fakes in tests |
| languages | ["python","typescript","javascript","go","rust","java","csharp","php","ruby"] |
| subagents | ["test/unit","test/integration"] |
| tools_needed | [] |
Mocking is a testing technique that replaces real implementations with controlled substitutes. This guide defines when to mock, what to mock, and common anti-patterns to avoid.
The Golden Rule: Mock only external dependencies (I/O boundaries), never internal logic.
Why?
Reason: Tests should be fast, isolated, and not require running database
Example (Python with pytest):
from unittest.mock import Mock
import pytest
def test_get_user_by_id():
# Mock the database connection
mock_db = Mock()
mock_db.query.return_value.filter.return_value.first.return_value = {
'id': '123',
'email': 'test@example.com'
}
user_service = UserService(db=mock_db)
user = user_service.get_user_by_id('123')
assert user['email'] == 'test@example.com'
mock_db.query.assert_called_once()
Exception: Integration tests should use REAL database (test database or in-memory)
Reason: External APIs are slow, cost money, have rate limits, can be down
Example (TypeScript with Vitest):
import { describe, it, expect, vi } from 'vitest'
import { PaymentService } from './payment-service'
describe('PaymentService', () => {
it('processes payment via Stripe API', async () => {
// Mock the HTTP client
const mockHttpClient = {
post: vi.fn().mockResolvedValue({
data: { id: 'charge_123', status: 'succeeded' }
})
}
const paymentService = new PaymentService(mockHttpClient)
const result = await paymentService.charge(100, 'tok_visa')
expect(result.status).toBe('succeeded')
expect(mockHttpClient.post).toHaveBeenCalledWith(
'/v1/charges',
expect.objectContaining({ amount: 100, source: 'tok_visa' })
)
})
})
Better Alternative: Use a mock server library (MSW, nock, WireMock)
Reason: File I/O is slow, tests should not depend on disk state
Example (Python):
from unittest.mock import mock_open, patch
def test_read_config():
mock_file_content = "database_url=postgres://localhost"
with patch('builtins.open', mock_open(read_data=mock_file_content)):
config = ConfigLoader.load('/etc/config.txt')
assert config['database_url'] == 'postgres://localhost'
Alternative: Use temp directories for integration tests
Reason: Tests should be deterministic, not dependent on current time
Example (Python):
from unittest.mock import patch
from datetime import datetime
def test_is_expired():
# Mock datetime.now() to return fixed time
fixed_time = datetime(2026, 1, 1, 12, 0, 0)
with patch('myapp.utils.datetime') as mock_datetime:
mock_datetime.now.return_value = fixed_time
token = Token(expires_at=datetime(2025, 12, 31))
assert token.is_expired() is True
Better Alternative: Inject clock/time provider
Reason: Tests should be reproducible
Example (TypeScript):
import { vi } from 'vitest'
it('generates random session ID', () => {
vi.spyOn(Math, 'random').mockReturnValue(0.5)
const sessionId = generateSessionId()
expect(sessionId).toBe('expected-deterministic-id')
})
Better Alternative: Inject random number generator
Reason: Should not send real emails/SMS during tests
Example (Python):
from unittest.mock import Mock
def test_send_welcome_email():
mock_email_client = Mock()
user_service = UserService(email_client=mock_email_client)
user_service.register_user('test@example.com', 'password')
mock_email_client.send.assert_called_once_with(
to='test@example.com',
subject='Welcome!',
body=expect.any(str)
)
Problem: Defeats the purpose of the test
❌ Wrong:
def test_calculate_total():
mock_calculator = Mock()
mock_calculator.calculate_total.return_value = 100
# Not testing anything!
assert mock_calculator.calculate_total([10, 20, 30]) == 100
✓ Correct:
def test_calculate_total():
calculator = Calculator()
# Actually tests the logic
assert calculator.calculate_total([10, 20, 30]) == 60
Problem: Tests implementation, not behavior
❌ Wrong:
def test_process_order():
mock_validate = Mock(return_value=True)
with patch('myapp.order.validate_order', mock_validate):
result = process_order(order_data)
# Not testing validation logic!
assert result.status == 'processed'
✓ Correct:
def test_process_order():
# Test through public interface - validation happens internally
result = process_order(valid_order_data)
assert result.status == 'processed'
def test_process_order_with_invalid_data():
# Test validation by providing invalid data
with pytest.raises(ValidationError):
process_order(invalid_order_data)
Lesson: Test internal helpers indirectly through public API
Problem: Integration tests should test real database interactions
❌ Wrong:
// Integration test with mocked database
it('creates user in database', async () => {
const mockDb = { insert: vi.fn().mockResolvedValue({ id: '123' }) }
const userRepo = new UserRepository(mockDb)
await userRepo.create({ email: 'test@example.com' })
// Not actually testing database!
})
✓ Correct:
// Integration test with real test database
it('creates user in database', async () => {
const testDb = await setupTestDatabase()
const userRepo = new UserRepository(testDb)
const user = await userRepo.create({ email: 'test@example.com' })
// Verify by querying database
const found = await testDb.query('SELECT * FROM users WHERE id = $1', [user.id])
expect(found.email).toBe('test@example.com')
await teardownTestDatabase(testDb)
})
For unit tests: Mock database. For integration tests: Use real database.
Problem: Standard library is well-tested, mocking adds no value
❌ Wrong:
from unittest.mock import patch
def test_parse_json():
with patch('json.loads', return_value={'key': 'value'}):
result = parse_config('{"key": "value"}')
# Not testing anything - json.loads is mocked!
✓ Correct:
def test_parse_json():
# Use real json.loads - it's fast and reliable
result = parse_config('{"key": "value"}')
assert result['key'] == 'value'
Problem: Mocking adds complexity without benefit
❌ Wrong:
it('calculates cart total', () => {
const mockCart = {
items: vi.fn().mockReturnValue([
{ price: 10 },
{ price: 20 }
])
}
// Overly complex
})
✓ Correct:
it('calculates cart total', () => {
const cart = new Cart()
cart.addItem({ price: 10 })
cart.addItem({ price: 20 })
expect(cart.total()).toBe(30)
})
Definition: Returns fixed data, no behavior verification
When to use: Simple test cases where you only care about return value
Example:
def test_get_user():
# Stub: returns fixed data
stub_db = Mock()
stub_db.get_user.return_value = User(id='123', email='test@example.com')
service = UserService(db=stub_db)
user = service.get_user('123')
assert user.email == 'test@example.com'
# No verification of how stub was called
Definition: Returns data AND verifies behavior (method calls, arguments)
When to use: When you need to verify interactions
Example:
def test_send_notification():
# Mock: verify it was called correctly
mock_email = Mock()
notifier = Notifier(email_client=mock_email)
notifier.send_welcome('test@example.com')
# Verify behavior
mock_email.send.assert_called_once_with(
to='test@example.com',
subject='Welcome',
body=expect.stringContaining('welcome')
)
Definition: Working implementation with shortcuts (in-memory database, fake API)
When to use: When you need realistic behavior without external dependencies
Example:
// Fake in-memory database
class FakeDatabase {
private users: Map<string, User> = new Map()
async insert(user: User): Promise<User> {
this.users.set(user.id, user)
return user
}
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null
}
}
it('creates and retrieves user', async () => {
const fakeDb = new FakeDatabase()
const repo = new UserRepository(fakeDb)
await repo.create({ id: '123', email: 'test@example.com' })
const user = await repo.findById('123')
expect(user?.email).toBe('test@example.com')
})
Benefits:
Definition: Wraps real object to record calls while preserving real behavior
When to use: When you want real behavior but also need to verify calls
Example:
import { vi } from 'vitest'
it('logs errors when payment fails', async () => {
const logger = new Logger()
const loggerSpy = vi.spyOn(logger, 'error')
const service = new PaymentService(logger)
await service.charge(100, 'invalid_token')
// Logger actually logs AND we can verify it was called
expect(loggerSpy).toHaveBeenCalledWith(
'Payment failed',
expect.objectContaining({ token: 'invalid_token' })
)
})
Principle: Pass dependencies as constructor/function arguments
✓ Good (testable):
class UserService:
def __init__(self, db, email_client):
self.db = db
self.email_client = email_client
def register(self, email, password):
user = self.db.insert_user(email, password)
self.email_client.send_welcome(email)
return user
# Easy to test - inject mocks
def test_register():
mock_db = Mock()
mock_email = Mock()
service = UserService(db=mock_db, email_client=mock_email)
service.register('test@example.com', 'password')
mock_db.insert_user.assert_called_once()
mock_email.send_welcome.assert_called_once()
❌ Bad (hard to test):
class UserService:
def __init__(self):
# Hard-coded dependencies
self.db = Database()
self.email_client = EmailClient()
def register(self, email, password):
# Can't inject mocks!
user = self.db.insert_user(email, password)
self.email_client.send_welcome(email)
return user
Principle: Define interfaces, mock implementations
Example (TypeScript):
// Define interface
interface PaymentGateway {
charge(amount: number, token: string): Promise<ChargeResult>
}
// Real implementation
class StripeGateway implements PaymentGateway {
async charge(amount: number, token: string): Promise<ChargeResult> {
// Real Stripe API call
}
}
// Mock implementation
class MockPaymentGateway implements PaymentGateway {
async charge(amount: number, token: string): Promise<ChargeResult> {
return { id: 'mock_charge', status: 'succeeded' }
}
}
// Service uses interface
class PaymentService {
constructor(private gateway: PaymentGateway) {}
async processPayment(amount: number, token: string) {
const result = await this.gateway.charge(amount, token)
return result
}
}
// Test with mock
it('processes payment', async () => {
const mockGateway = new MockPaymentGateway()
const service = new PaymentService(mockGateway)
const result = await service.processPayment(100, 'tok_visa')
expect(result.status).toBe('succeeded')
})
Principle: Use builders to create test objects with sensible defaults
Example:
class UserBuilder:
def __init__(self):
self.id = 'default-id'
self.email = 'default@example.com'
self.is_active = True
def with_id(self, id):
self.id = id
return self
def with_email(self, email):
self.email = email
return self
def inactive(self):
self.is_active = False
return self
def build(self):
return User(id=self.id, email=self.email, is_active=self.is_active)
# Clean test code
def test_send_email_to_active_users():
active_user = UserBuilder().with_email('active@example.com').build()
inactive_user = UserBuilder().inactive().build()
email_service.send_newsletter([active_user, inactive_user])
# Only active user receives email
assert_email_sent_to('active@example.com')
assert_email_not_sent_to('inactive@example.com')
Problem: Tests become brittle, don't test real behavior
❌ Wrong:
def test_process_order():
mock_validate = Mock(return_value=True)
mock_calculate = Mock(return_value=100)
mock_save = Mock()
mock_email = Mock()
with patch.multiple('myapp.order',
validate=mock_validate,
calculate_total=mock_calculate,
save_order=mock_save,
send_confirmation=mock_email):
process_order(order_data)
# Not testing any real logic!
✓ Better:
def test_process_order():
# Only mock external dependencies
mock_db = Mock()
mock_email = Mock()
service = OrderService(db=mock_db, email=mock_email)
result = service.process_order(order_data)
# Real validation and calculation happen
assert result.total == 100
mock_db.save.assert_called_once()
mock_email.send.assert_called_once()
Problem: Tests break when irrelevant details change
❌ Brittle:
mock_api.post.assert_called_once_with(
'/users',
headers={'Content-Type': 'application/json', 'User-Agent': 'MyApp/1.0'},
json={'email': 'test@example.com', 'name': 'Test', 'age': 25},
timeout=30
)
✓ Resilient:
mock_api.post.assert_called_once()
call_args = mock_api.post.call_args
assert call_args[0][0] == '/users'
assert call_args[1]['json']['email'] == 'test@example.com'
# Don't assert on headers, timeout unless critical
Problem: Test verifies mock setup, not actual logic
❌ Wrong:
def test_mock_returns_correct_value():
mock_service = Mock()
mock_service.get_user.return_value = User(id='123')
# This just tests that the mock works - useless!
assert mock_service.get_user().id == '123'
Problem: Test pollution - tests affect each other
❌ Wrong:
mock_db = Mock() # Shared across tests
def test_create_user():
service = UserService(db=mock_db)
service.create_user('test@example.com')
assert mock_db.insert.call_count == 1
def test_create_another_user():
service = UserService(db=mock_db)
service.create_user('test2@example.com')
# Fails! call_count is 2 because previous test's call is still tracked
assert mock_db.insert.call_count == 1
✓ Correct:
import pytest
@pytest.fixture
def mock_db():
# Fresh mock for each test
return Mock()
def test_create_user(mock_db):
service = UserService(db=mock_db)
service.create_user('test@example.com')
assert mock_db.insert.call_count == 1
from unittest.mock import Mock, patch, MagicMock
import pytest
# Use fixtures for mocks
@pytest.fixture
def mock_db():
return Mock()
# Use patch for global dependencies
def test_with_patching():
with patch('myapp.utils.send_email') as mock_email:
send_welcome_email('test@example.com')
mock_email.assert_called_once()
# Use MagicMock for magic methods
def test_context_manager():
mock_file = MagicMock()
with mock_file as f:
f.read.return_value = 'content'
import { describe, it, expect, vi, beforeEach } from 'vitest'
describe('UserService', () => {
let mockDb: any
beforeEach(() => {
mockDb = {
insert: vi.fn(),
findById: vi.fn()
}
})
it('creates user', async () => {
mockDb.insert.mockResolvedValue({ id: '123' })
const service = new UserService(mockDb)
const user = await service.create({ email: 'test@example.com' })
expect(user.id).toBe('123')
expect(mockDb.insert).toHaveBeenCalledOnce()
})
})
Before adding a mock to your test, ask:
Golden Rules: