Mocks vs. Stubs: Choosing the Right Tool for the Job
Understanding the Key Differences and Uses of Mocks and Stubs in Unit Testing
Mock and stub are tools used in unit testing, a type of software testing that involves testing individual units or components of a software application to verify that they are working as expected. A stub is a dummy implementation of a method or object that is used as a placeholder in a test, while a mock is a fake implementation of a method or object that is used to simulate its behavior in a test.
The main difference between mock and stub is that a stub is used to provide a specific response to a method call in order to control the flow of the test, while a mock is used to verify the behavior of the code under test by checking whether the correct methods were called and whether they were called with the correct arguments.
This means that a stub is used to control the test, while a mock is used to assert the behavior of the code under test.
In general, stubs are used to replace real implementations of methods or objects in order to simplify the test, while mocks are used to verify the behavior of the code under test. This allows developers to write more focused and more efficient tests, which can help them find and fix defects in their code more quickly and easily.
Stub
Here is an example of using a stub in a unit test in JavaScript:
export class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
find(userId) {
try {
return this.userRepository.get(userId);
} catch (err) {
return null;
}
}
}
import { UserService } from "./UserService";
describe('UserService', () => {
// This is a unit test
test('returns the user on success', () => {
// Create a stub for the user repository
const userRepositoryStub = { get: jest.fn() }
// Set the stub to return a specific value
userRepositoryStub.get.mockReturnValue({ id: 1, role: 'admin' });
// Use the stub in the code under test
const sut = new UserService(userRepositoryStub);
// Call the method under test
const user = sut.find(1);
// Verify that the code under test did what it was supposed to do
expect(user).toEqual({ id: 1, role: 'admin' })
});
// This is a unit test
test('returns null on failure', () => {
const userRepositoryStub = { get: jest.fn() }
// Set the stub to return a specific value
userRepositoryStub.get.mockImplementation(() => { throw new Error });
// Use the stub in the code under test
const sut = new UserService(userRepositoryStub);
// Call the method under test
const user = sut.find(1);
// Verify that the code under test did what it was supposed to do
expect(user).toEqual(null)
});
})
In this example, the stub controls the test flow by providing a specific value to the code under test.
Mock
Here is an example of using a mock in a unit test in JavaScript:
export class EmailService {
constructor(mailClient) {
this.mailClient = mailClient;
}
sendWelcomeEmail() {
this.mailClient.send("john@doe.com", "Welcome", "Lorem ipsum.");
}
}
import { EmailService } from './EmailService'
describe('EmailService', () => {
// This is the unit test
test('sends welcome email', () => {
// Create a mock for the mail client
const mailClientMock = { send: jest.fn() }
// Use the mock in the code under test
const sut = new EmailService(mailClientMock);
// Call the method under test
sut.sendWelcomeEmail();
// Verify that the code under test called the mock with the correct arguments
expect(mailClientMock.send).toHaveBeenCalledWith("john@doe.com", "Welcome", "Lorem ipsum.")
});
})
In this example, the mock is used to assert the code's behavior under test by verifying that it called the send()
method on the mailClientMock
object with the correct arguments.