| name | mocking-stubbing |
| description | Create and manage mocks, stubs, spies, and test doubles for isolating unit tests from external dependencies. Use for mock, stub, spy, test double, Mockito, Jest mocks, and dependency isolation. |
Mocking and Stubbing
Overview
Mocking and stubbing are essential techniques for isolating units of code during testing by replacing dependencies with controlled test doubles. This enables fast, reliable, and focused unit tests that don't depend on external systems like databases, APIs, or file systems.
When to Use
- Isolating unit tests from external dependencies
- Testing code that depends on slow operations (DB, network)
- Simulating error conditions and edge cases
- Verifying interactions between objects
- Testing code with non-deterministic behavior (time, randomness)
- Avoiding expensive operations in tests
- Testing error handling without triggering real failures
Test Double Types
- Stub: Returns predefined values, no behavior verification
- Mock: Verifies interactions (method calls, arguments)
- Spy: Wraps real object, allows partial mocking
- Fake: Working implementation, but simplified (in-memory DB)
- Dummy: Passed but never used (fills parameter lists)
Instructions
1. Jest Mocking (JavaScript/TypeScript)
Basic Mocking
import { UserRepository } from './UserRepository';
import { EmailService } from './EmailService';
export class UserService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService
) {}
async createUser(userData: CreateUserDto) {
const user = await this.userRepository.create(userData);
await this.emailService.sendWelcomeEmail(user.email, user.name);
return user;
}
async getUserStats(userId: string) {
const user = await this.userRepository.findById(userId);
if (!user) throw new Error('User not found');
const orderCount = await this.userRepository.getOrderCount(userId);
return { ...user, orderCount };
}
}
import { UserService } from '../UserService';
import { UserRepository } from '../UserRepository';
import { EmailService } from '../EmailService';
jest.mock('../UserRepository');
jest.mock('../EmailService');
describe('UserService', () => {
let userService: UserService;
let mockUserRepository: jest.Mocked<UserRepository>;
let mockEmailService: jest.Mocked<EmailService>;
beforeEach(() => {
jest.clearAllMocks();
mockUserRepository = new UserRepository() as jest.Mocked<UserRepository>;
mockEmailService = new EmailService() as jest.Mocked<EmailService>;
userService = new UserService(mockUserRepository, mockEmailService);
});
describe('createUser', () => {
it('should create user and send welcome email', async () => {
const userData = {
email: 'test@example.com',
name: 'Test User',
password: 'password123'
};
const createdUser = {
id: '123',
...userData,
createdAt: new Date()
};
mockUserRepository.create.mockResolvedValue(createdUser);
mockEmailService.sendWelcomeEmail.mockResolvedValue(undefined);
const result = await userService.createUser(userData);
expect(result).toEqual(createdUser);
expect(mockUserRepository.create).toHaveBeenCalledWith(userData);
expect(mockUserRepository.create).toHaveBeenCalledTimes(1);
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith(
userData.email,
userData.name
);
});
it('should not send email if user creation fails', async () => {
mockUserRepository.create.mockRejectedValue(
new Error('Database error')
);
await expect(
userService.createUser({ email: 'test@example.com' })
).rejects.toThrow('Database error');
expect(mockEmailService.sendWelcomeEmail).not.toHaveBeenCalled();
});
});
describe('getUserStats', () => {
it('should return user with order count', async () => {
const userId = '123';
const user = { id: userId, name: 'Test User' };
mockUserRepository.findById.mockResolvedValue(user);
mockUserRepository.getOrderCount.mockResolvedValue(5);
const result = await userService.getUserStats(userId);
expect(result).toEqual({ ...user, orderCount: 5 });
expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
expect(mockUserRepository.getOrderCount).toHaveBeenCalledWith(userId);
});
it('should throw error if user not found', async () => {
mockUserRepository.findById.mockResolvedValue(null);
await expect(userService.getUserStats('999')).rejects.toThrow(
'User not found'
);
expect(mockUserRepository.getOrderCount).not.toHaveBeenCalled();
});
});
});
Spying on Functions
const stripe = require('stripe');
class PaymentService {
async processPayment(amount, currency, customerId) {
const charge = await stripe.charges.create({
amount: amount * 100,
currency,
customer: customerId,
});
this.logPayment(charge.id, amount);
return charge;
}
logPayment(chargeId, amount) {
console.log(`Payment processed: ${chargeId} for $${amount}`);
}
}
describe('PaymentService', () => {
let paymentService;
let stripeMock;
beforeEach(() => {
stripeMock = {
charges: {
create: jest.fn(),
},
};
jest.mock('stripe', () => jest.fn(() => stripeMock));
paymentService = new PaymentService();
});
it(, () => {
mockCharge = { : , : };
stripeMock...(mockCharge);
logSpy = jest.(paymentService, );
paymentService.(, , );
(stripeMock..).({
: ,
: ,
: ,
});
(logSpy).(, );
logSpy.();
});
});
2. Python Mocking with unittest.mock
from typing import Optional
from repositories.order_repository import OrderRepository
from services.payment_service import PaymentService
from services.notification_service import NotificationService
class OrderService:
def __init__(
self,
order_repository: OrderRepository,
payment_service: PaymentService,
notification_service: NotificationService
):
self.order_repository = order_repository
self.payment_service = payment_service
self.notification_service = notification_service
def create_order(self, user_id: str, items: list) -> Order:
"""Create and process a new order."""
order = self.order_repository.create({
'user_id': user_id,
'items': items,
'status': 'pending'
})
try:
payment = self.payment_service.process_payment(
order.id,
order.total
)
order.status = 'paid'
order.payment_id = payment.id
self.order_repository.update(order)
self.notification_service.send_order_confirmation(
order.user_id,
order.id
)
except PaymentError e:
order.status =
.order_repository.update(order)
order
pytest
unittest.mock Mock, MagicMock, patch, call
services.order_service OrderService
exceptions PaymentError
:
():
{
: Mock(),
: Mock(),
: Mock()
}
():
OrderService(**mock_dependencies)
():
user_id =
items = [{: , : }]
mock_order = Mock(
=,
total=,
status=,
user_id=user_id
)
mock_payment = Mock(=)
mock_dependencies[].create.return_value = mock_order
mock_dependencies[].process_payment.return_value = mock_payment
result = order_service.create_order(user_id, items)
result.status ==
result.payment_id ==
mock_dependencies[].create.assert_called_once_with({
: user_id,
: items,
:
})
mock_dependencies[].process_payment.assert_called_once_with(
,
)
mock_dependencies[].send_order_confirmation.assert_called_once_with(
user_id,
)
mock_dependencies[].update.call_count ==
():
mock_order = Mock(=, total=, status=)
mock_dependencies[].create.return_value = mock_order
mock_dependencies[].process_payment.side_effect = PaymentError()
pytest.raises(PaymentError):
order_service.create_order(, [])
mock_order.status ==
mock_dependencies[].update.assert_called()
mock_dependencies[].send_order_confirmation.assert_not_called()
():
fixed_time = datetime(, , , , , )
mock_datetime.now.return_value = fixed_time
mock_order = Mock(=, created_at=fixed_time)
mock_dependencies[].create.return_value = mock_order
result = order_service.create_order(, [])
result.created_at == fixed_time
3. Mockito for Java
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditLogger auditLogger;
public UserService(
UserRepository userRepository,
EmailService emailService,
AuditLogger auditLogger
) {
this.userRepository = userRepository;
this.emailService = emailService;
this.auditLogger = auditLogger;
}
public User createUser(UserDto userDto) {
User user = userRepository.save(mapToUser(userDto));
emailService.sendWelcomeEmail(user.getEmail());
auditLogger.log("User created: " + user.getId());
return user;
}
public Optional<User> getUserWithOrders(Long userId) {
return userRepository.findByIdWithOrders(userId);
}
}
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@Mock
private AuditLogger auditLogger;
UserService userService;
{
(, );
(, , );
(userRepository.save(any(User.class))).thenReturn(savedUser);
doNothing().(emailService).sendWelcomeEmail(anyString());
userService.createUser(userDto);
assertNotNull(result);
assertEquals(, result.getId());
verify(userRepository, times()).save(any(User.class));
verify(emailService, times()).sendWelcomeEmail();
verify(auditLogger, times()).log(contains());
}
{
(, );
(, , );
(userRepository.save(any(User.class))).thenReturn(savedUser);
doThrow( ())
.(emailService)
.sendWelcomeEmail(anyString());
assertThrows(EmailException.class, () -> {
userService.createUser(userDto);
});
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail();
}
{
(, , );
(userRepository.findByIdWithOrders())
.thenReturn(Optional.of(user));
Optional<User> result = userService.getUserWithOrders();
assertTrue(result.isPresent());
assertEquals(user, result.get());
verify(userRepository).findByIdWithOrders();
}
{
(userRepository.findByIdWithOrders())
.thenReturn(Optional.empty());
Optional<User> result = userService.getUserWithOrders();
assertFalse(result.isPresent());
}
ArgumentCaptor<User> userCaptor;
{
(, );
(userRepository.save(any(User.class)))
.thenReturn( (, , ));
userService.createUser(userDto);
verify(userRepository).save(userCaptor.capture());
userCaptor.getValue();
assertEquals(, capturedUser.getEmail());
assertEquals(, capturedUser.getName());
}
}
4. Advanced Mocking Patterns
describe('Scheduled Tasks', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('should execute task after delay', () => {
const callback = jest.fn();
const scheduler = new TaskScheduler();
scheduler.scheduleTask(callback, 5000);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(5000);
expect(callback).toHaveBeenCalledTimes(1);
});
});
describe('UserService with partial mocking', () => {
it('should use real method for validation, mock for DB', async () => {
const userService = new UserService();
const saveSpy = jest
.spyOn(userService.repository, 'save')
.mockResolvedValue({ id: '123' });
(
userService.({ : })
)..();
(saveSpy)..();
userService.({ : });
(saveSpy).();
});
});
Best Practices
✅ DO
- Mock external dependencies (DB, API, file system)
- Use dependency injection for easier mocking
- Verify important interactions with mocks
- Reset mocks between tests
- Mock at the boundary (repositories, services)
- Use spies for partial mocking when needed
- Create reusable mock factories
- Test both success and failure scenarios
❌ DON'T
- Mock everything (don't mock what you own)
- Over-specify mock interactions
- Use mocks in integration tests
- Mock simple utility functions
- Create complex mock hierarchies
- Forget to verify mock calls
- Share mocks between tests
- Mock just to make tests pass
Tools & Libraries
- JavaScript/TypeScript: Jest, Sinon.js, ts-mockito
- Python: unittest.mock, pytest-mock, responses
- Java: Mockito, EasyMock, PowerMock, JMockit
- C#: Moq, NSubstitute, FakeItEasy
Examples
See also: integration-testing, test-data-generation, test-automation-framework for complete testing patterns.