Definition
Test doubles are objects that replace production dependencies in tests to provide isolation and control. They allow testing units in isolation by removing dependencies on external systems, slow operations, or unpredictable components.
Types of Test Doubles
Dummy
Objects passed around but never actually used. Usually just to fill parameter lists.
def test_user_creation():
dummy_logger = DummyLogger() # Never called in this test
user = User.create("john", dummy_logger)
assert user.name == "john"
Fake
Working implementations with simplified behavior, usually for testing purposes.
class FakeDatabase:
def __init__(self):
self.data = {}
def save(self, key, value):
self.data[key] = value
def get(self, key):
return self.data.get(key)
Stub
Provides predetermined responses to calls made during the test.
email_service_stub = Mock()
email_service_stub.send_email.return_value = True
Spy
Records information about how they were called, allowing verification of interactions.
database_spy = Mock()
user_service.delete_user("123", database_spy)
database_spy.delete.assert_called_once_with("users", "123")
Mock
Pre-programmed with expectations about calls they will receive and can throw exceptions if unexpected calls are made.
payment_mock = Mock()
payment_mock.process_payment.return_value = {"status": "success"}
# Verify the mock was called as expected
payment_mock.process_payment.assert_called_with(amount=100, currency="USD")
When to Use Test Doubles
- External dependencies: Databases, web services, file systems
- Slow operations: Network calls, disk I/O, complex calculations
- Non-deterministic behavior: Random number generators, current time
- Difficult-to-reproduce conditions: Error states, edge cases
- Unimplemented dependencies: Components not yet built
Best Practices
- Use the simplest type of double that meets your needs
- Prefer real objects when possible and practical
- Keep test doubles simple and focused
- Verify interactions on important collaborators
- Avoid over-mocking - test behavior, not implementation
Common Pitfalls
- Over-mocking: Mocking too many collaborators makes tests brittle
- Mock leakage: Test doubles affecting other tests
- Verifying implementation: Testing how something is done rather than what is done
- Complex test doubles: Creating doubles that are harder to understand than real objects