With nearly two decades of experience in the tech industry, I know that writing robust unit tests is crucial for developing reliable software. However, external dependencies—like APIs, databases, or third-party services—can make unit tests slower, harder to maintain, and less reliable. This is where mocking and stubbing come in. These techniques help isolate your tests, simulating dependencies to ensure your code works as expected without external interference.
In this tech post, we’ll explore the differences between mocks and stubs, show how to implement them, and share examples using popular testing frameworks like Jest (JavaScript), Mockito (Java), and pytest (Python).
Understanding Mocks vs. Stubs
Before jumping into examples, let’s clarify the differences:
- Mocks simulate objects and allow you to verify how these objects are interacted with during testing. For example, you can check if a method was called and with what arguments.
- Stubs return predefined responses when invoked during tests. Stubs don’t verify interactions but simply provide data to ensure your test runs as expected.
Key Differences:
- Mocks are used to verify behavior and interactions.
- Stubs are used to provide fixed responses to avoid reliance on external dependencies.
Using Mocks in Unit Tests
Mocks are powerful tools for verifying that specific functions or methods were called with the correct parameters. They’re commonly used when you want to ensure interactions with third-party services, like APIs or databases.
Example: Mocking API Calls with Jest (JavaScript)
Here’s an example of how to mock an API request in JavaScript using Jest:
// userService.js
const axios = require('axios');
async function fetchUserData(userId) {
const response = await axios.get(`/api/users/${userId}`);
return response.data;
}
// userService.test.js
const axios = require('axios');
const { fetchUserData } = require('./userService');
jest.mock('axios');
test('should fetch user data from API', async () => {
const mockData = { id: 1, name: 'John Doe' };
axios.get.mockResolvedValue({ data: mockData });
const user = await fetchUserData(1);
expect(user).toEqual(mockData);
expect(axios.get).toHaveBeenCalledWith('/api/users/1');
});
In this example:
jest.mock('axios')
: Mocks theaxios
module to avoid making actual network requests.mockResolvedValue()
: Ensures the mock returns predefined data.toHaveBeenCalledWith()
: Verifies thataxios.get()
was called with the correct URL.
Mocking Best Practices:
- Use mocks to simulate and verify interactions with external services.
- Keep tests simple and focused on essential behaviors.
- Avoid mocking too many dependencies in one test, as it can make tests brittle.
Using Stubs in Unit Tests
Stubs are useful when you need to provide fixed responses from external dependencies but don’t care about the interaction details. They let you isolate your business logic and ensure that the test runs smoothly, even when complex systems like databases or file systems are involved.
Example: Stubbing Database Calls with Mockito (Java)
Here’s how to use stubs with Mockito to test a service that fetches data from a database:
// UserService.java
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int userId) {
return userRepository.findById(userId);
}
}
// UserServiceTest.java
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Test;
public class UserServiceTest {
@Test
public void testGetUserById() {
UserRepository mockRepo = mock(UserRepository.class);
UserService userService = new UserService(mockRepo);
User stubUser = new User(1, "John Doe");
when(mockRepo.findById(1)).thenReturn(stubUser);
User user = userService.getUserById(1);
assertEquals("John Doe", user.getName());
}
}
In this example:
mock(UserRepository.class)
: Creates a mock of theUserRepository
.when().thenReturn()
: Stubs the behavior offindById()
to return a predefined user.
Stubbing Best Practices:
- Use stubs to simplify external dependencies, especially when they slow down tests.
- Avoid over-stubbing; only stub what is necessary for the test.
Combining Mocks and Stubs
Sometimes, you need to both mock certain interactions and stub data responses. This is common when testing services that interact with both databases and APIs.
Example: Testing Email Notifications with pytest (Python)
Here’s how you can combine mocks and stubs in Python using pytest
:
# user_service.py
class UserService:
def __init__(self, user_repo, email_service):
self.user_repo = user_repo
self.email_service = email_service
def register_user(self, user):
self.user_repo.save(user)
self.email_service.send_email(user.email)
# test_user_service.py
from unittest.mock import Mock
def test_register_user():
# Mocking the dependencies
mock_user_repo = Mock()
mock_email_service = Mock()
user_service = UserService(mock_user_repo, mock_email_service)
# Creating a stub user
user = {"email": "[email protected]"}
# Running the test
user_service.register_user(user)
# Verifying that the user was saved and email was sent
mock_user_repo.save.assert_called_once_with(user)
mock_email_service.send_email.assert_called_once_with("[email protected]")
In this example:
- Mocks: Both
user_repo
andemail_service
are mocked to ensure they are used correctly. - Stubs: The user object acts as a simple stub.
- Verification: The test checks that both the user repository and email service were called with the expected data.
Combining Mocks and Stubs:
- Use mocks to verify external interactions.
- Use stubs to provide fixed data and isolate business logic.
When to Use Mocks and Stubs
- Mocks: Use them when you need to verify that specific functions or methods were called correctly, especially for interactions with third-party services.
- Stubs: Use them to provide simple, canned responses that isolate your tests from complex or slow external dependencies like databases or file systems.
My TechAdvice: Incorporating mocking and stubbing techniques in your unit testing strategy is a powerful way to isolate your tests from external dependencies and verify that your code is functioning as expected. This approach can help you create more reliable, scalable, and maintainable applications. Stubs allow you to isolate your code from external systems, speeding up your tests and making them more reliable. Whether you’re using Jest for JavaScript, Mockito for Java, or pytest for Python .. etc mastering mocks and stubs will improve the quality and speed of your unit tests, helping you catch bugs early and ensuring your code is both reliable and efficient.
#AskDushyant
#TechConcept #TechAdvice #Testing #SoftwareTesting #Mocking #Stubbing
Note: The example and pseudo code is for illustration only. You must modify and experiment with the concept to meet your specific needs.
Leave a Reply