Mocking in Tests: Complete Guide
Mocking is the technique of replacing real objects in your tests with simulated versions that behave predictably — letting you test a unit of code in isolation without relying on databases, APIs, file systems, or other external dependencies.
What You’ll Learn
- What mocking is and when (and when not) to use it
- Python
unittest.mockpatterns: Mock, patch, MagicMock - JavaScript Jest mocks: jest.fn(), jest.spyOn(), jest.mock()
- Spies, stubs, and fakes — the different types of test doubles
- Best practices to avoid over-mocking
Why Mocking Matters
Without mocking, a test for a service layer would need a real database, a real API endpoint, and a real file system. These dependencies make tests slow, flaky, and hard to set up. Mocking replaces them with lightweight, fast, predictable stand-ins. A well-mocked test suite runs in seconds, not minutes.
Durga Antivirus Pro uses extensive mocking in its scanner tests — replacing the real virus definition database with mock data so tests run quickly and don’t depend on database availability.
Learning Path
flowchart LR
A[Unit Testing Basics] --> B[When to Mock]
B --> C[Python unittest.mock<br/>You are here]
B --> D[Jest Mocks]
C --> E[Spies & Stubs]
D --> E
style C fill:#f90,color:#fff
style D fill:#f90,color:#fff
When to Mock (and When Not To)
Mock when:
- The dependency makes network calls (database, API, file system)
- The dependency is slow or unreliable
- The dependency is hard to set up (third-party auth, payment gateway)
- You need to test error conditions that are hard to trigger
Don’t mock when:
- The dependency is a simple value object or pure function
- The test becomes brittle (testing implementation details)
- You find yourself mocking everything — consider refactoring instead
Python: unittest.mock
Basic Mock
from unittest.mock import Mock, patch
import pytest
# Creating a mock object
mock_db = Mock()
mock_db.query.return_value = [{"id": 1, "name": "Alice"}]
# Using the mock
result = mock_db.query("SELECT * FROM users")
assert result == [{"id": 1, "name": "Alice"}]
mock_db.query.assert_called_once_with("SELECT * FROM users")Patch Decorator
# service.py
import requests
def get_user(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()
# test_service.py
from unittest.mock import patch
from service import get_user
@patch("service.requests.get")
def test_get_user(mock_get):
# Arrange
mock_response = Mock()
mock_response.json.return_value = {"id": 1, "name": "Alice"}
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
# Act
result = get_user(1)
# Assert
assert result == {"id": 1, "name": "Alice"}
mock_get.assert_called_once_with("https://api.example.com/users/1")Multiple Patches
@patch("service.email_service")
@patch("service.user_repository")
def test_register_user(mock_repo, mock_email):
mock_repo.find_by_email.return_value = None
mock_repo.save.return_value = True
result = register_user("alice@example.com", "password123")
assert result.success is True
mock_repo.save.assert_called_once()
mock_email.send_welcome.assert_called_once()Side Effects (Testing Errors)
import requests.exceptions
@patch("service.requests.get")
def test_get_user_network_error(mock_get):
mock_get.side_effect = requests.exceptions.ConnectionError("Network error")
with pytest.raises(requests.exceptions.ConnectionError):
get_user(1)
# Alternating return values
mock_db.query.side_effect = [
[{"id": 1}], # First call
[{"id": 2}], # Second call
[], # Third call
]JavaScript: Jest
jest.fn() — Basic Mock
// Basic mock function
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
console.log(mockFn()); // 42
expect(mockFn).toHaveBeenCalledTimes(1);
// With implementation
const add = jest.fn((a, b) => a + b);
expect(add(2, 3)).toBe(5);
expect(add).toHaveBeenCalledWith(2, 3);jest.spyOn() — Spying on Existing Methods
// userService.js
export const userService = {
async getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
};
// userService.test.js
import { userService } from './userService';
describe('getUser', () => {
it('returns user data', async () => {
const mockUser = { id: 1, name: 'Alice' };
// Spy on fetch and mock its return
jest.spyOn(global, 'fetch').mockResolvedValue({
json: async () => mockUser,
ok: true,
});
const result = await userService.getUser(1);
expect(result).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('/api/users/1');
// Clean up
jest.restoreAllMocks();
});
});jest.mock() — Module Mocking
// database.js
export const db = {
async query(sql, params) {
// Real database call
},
};
// userRepository.js
import { db } from './database';
export async function findUserByEmail(email) {
const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
return result.rows[0];
}
// userRepository.test.js
jest.mock('./database', () => ({
db: {
query: jest.fn(),
},
}));
import { findUserByEmail } from './userRepository';
import { db } from './database';
test('finds user by email', async () => {
db.query.mockResolvedValue({
rows: [{ id: 1, email: 'alice@example.com', name: 'Alice' }],
});
const user = await findUserByEmail('alice@example.com');
expect(user.name).toBe('Alice');
expect(db.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE email = $1',
['alice@example.com']
);
});Spies — Verify Calls Without Changing Behavior
const logger = { info: (msg) => console.log(msg) };
const spy = jest.spyOn(logger, 'info');
processOrder(order);
expect(spy).toHaveBeenCalledWith('Order processed: 123');
// Original logger.info still works
spy.mockRestore(); // Restore original
Test Double Types
| Type | Purpose | Example |
|---|---|---|
| Stub | Returns fixed values | mock.return_value = 42 |
| Spy | Records calls without changing behavior | jest.spyOn(obj, 'method') |
| Mock | Pre-programmed with expectations | mock.assert_called_once_with(x) |
| Fake | Lightweight working implementation | In-memory database instead of real one |
Common Errors
1. Over-Mocking
Mocking everything makes tests brittle — they break when implementation details change. Mock boundaries (external APIs, databases) but not internals.
2. Not Restoring Mocks
If a mock isn’t restored, it leaks to other tests. Use patch as a decorator/context manager or call mock.restore() in afterEach.
3. Mocking the Wrong Thing
Patch the import path where the function is used, not where it’s defined. @patch("service.requests.get") patches requests.get in service.py, not requests.py.
4. Asserting Too Much
Only assert what’s necessary for the behavior under test. Over-asserting makes tests fragile.
5. Mocking Value Objects
Don’t mock data classes, DTOs, or config objects. Use real instances.
6. Forgetting to Test Error Paths
Mock side_effect to test how your code handles network errors, timeouts, and invalid responses.
Practice Questions
What is a mock? A simulated object that replaces a real dependency, returning pre-programmed values and recording how it was called.
When should you NOT mock? When the dependency is a simple value object, pure function, or when mocking makes the test brittle.
How do you patch an external API call in Python? Use
@patch("module.requests.get")— patch at the import location where it’s used.What’s the difference between a stub and a mock? A stub returns fixed values. A mock has expectations about how it should be called and asserts against those expectations.
What does jest.spyOn do? Creates a spy that wraps an existing method, recording calls without changing the original behavior.
Challenge: Write a test suite for a service that fetches user data from an API, stores it in a database, and sends a welcome email. Mock all three external dependencies. Test the success path and two error paths (API failure, database failure).
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Jest Testing Framework | Complete Jest guide with advanced patterns |
| Integration Testing Guide | Testing with real dependencies — the alternative to mocking |
| Unit Testing Best Practices | Foundation concepts for effective testing |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-19.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro