Mocks and stubs are both types of test doubles used in unit testing to simulate the behavior of complex objects or systems. They help in isolating the unit under test by providing controlled and predictable responses. However, they serve different purposes and are used in different contexts. Here’s a detailed comparison:
1. Purpose
- Stub: A stub is used to provide predetermined responses to specific inputs. It is primarily used to replace real components that the unit under test depends on, allowing the test to run in isolation. Stubs are often used when the test doesn’t need to verify how the dependency interacts with the system, but rather just needs to provide controlled responses.
- Mock: A mock not only simulates the behavior of a real object but also records how it was used, allowing tests to verify interactions with the mock object. Mocks are used when the behavior of the object (e.g., method calls, parameters passed, etc.) needs to be asserted, making them useful for checking that certain interactions occurred as expected.
2. Verification
- Stub: Stubs typically do not track how they are used or provide any mechanisms for verification of behavior. They focus on supplying fixed responses and are generally passive in nature.
- Mock: Mocks actively record their usage and provide ways to verify that specific interactions occurred. For example, mocks can assert that a particular method was called a certain number of times, with specific arguments. This makes them useful for behavioral testing.
3. Complexity and Use Cases
- Stub: Stubs are generally simpler and are used when you need to isolate the unit under test from its dependencies, providing predefined responses without caring about the interaction. Example Use Case: If you’re testing a function that sends an email, you might use a stub to simulate the email service and return a success response, without actually sending an email.
- Mock: Mocks are more complex and are used when the interactions with the object are part of what needs to be tested. They are useful when the test needs to verify that certain methods were called in specific ways. Example Use Case: If you’re testing a method that logs actions, you might use a mock to check that the logging method was called with the correct messages and parameters.
4. Example in Python
Stub Example:
from unittest import TestCase
class EmailServiceStub:
def send_email(self, to, subject, body):
return True # Pretend the email is sent successfully
class EmailSender:
def __init__(self, email_service):
self.email_service = email_service
def send(self, to, subject, body):
return self.email_service.send_email(to, subject, body)
class TestEmailSender(TestCase):
def test_send_email(self):
email_service_stub = EmailServiceStub()
email_sender = EmailSender(email_service_stub)
result = email_sender.send('test@example.com', 'Test', 'Test Body')
self.assertTrue(result)
In this example, EmailServiceStub
provides a predefined response (always returning True
) to simulate the behavior of a real email service. The test doesn’t verify anything about the email service other than that it allows the EmailSender
class to function.
Mock Example:
from unittest import TestCase
from unittest.mock import Mock
class Logger:
def log(self, message):
pass # Assume this logs to a file or external service
class Processor:
def __init__(self, logger):
self.logger = logger
def process(self, data):
# Some processing logic...
self.logger.log("Processed data")
class TestProcessor(TestCase):
def test_process_logging(self):
logger_mock = Mock()
processor = Processor(logger_mock)
processor.process("sample data")
logger_mock.log.assert_called_once_with("Processed data")
In this example, logger_mock
is a mock object that simulates the Logger
class. The test verifies that the log
method is called exactly once with the argument “Processed data”. This is a behavioral verification, ensuring that the method interacts with the Logger
as expected.
Summary
- Stubs are used for providing fixed responses and do not record how they are used. They are best for when the behavior of the dependency is not being tested.
- Mocks are used to verify interactions and assert that certain behaviors occur. They are best for testing that specific interactions with the dependency happen as expected.
Understanding when to use stubs versus mocks is crucial in writing effective unit tests that not only validate the correctness of your code but also ensure that it behaves as expected in its interactions with other components.