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

Links