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:
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.
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.