Jest
jest.mock()
vs jest.spyOn()
​
Both jest.mock()
and jest.spyOn()
are powerful features of Jest that serve similar, yet distinct purposes when it comes to mocking functions and modules in your tests. Understanding the differences between them is crucial for effectively mocking dependencies and ensuring your tests are both isolated and focused on the unit under test. Here's a breakdown of the main differences:
jest.mock()
​
- Module-level Mocking:
jest.mock()
is used for mocking entire modules. It replaces all exports of the module with Jest mock functions. This approach is useful when you want to mock every function exported by a module without calling the actual implementation. - Automatic Mocking: By default,
jest.mock()
automatically mocks the module with jest's mock functions that can be customized. This is particularly useful for modules with side effects or modules that you want to prevent from running in your test environment. - Hoisted to the Top: Calls to
jest.mock()
are hoisted to the top of the code block, which means they're executed before any imports. This ensures that the module is mocked before it's used anywhere in your tests.
Example:
jest.mock('moduleName'); // Mocks moduleName entirely
jest.spyOn()
​
- Function-level Mocking:
jest.spyOn()
is used for spying on the methods of objects. This includes spying on the implementation of a function, allowing you to call through the actual implementation, record calls to the function, or mock return values or implementations for specific calls. - Preserves the Original Module/Function: Unlike
jest.mock()
,jest.spyOn()
does not automatically mock the function. It wraps the function so you can monitor calls to it or replace the implementation, but the original implementation can still be called if desired. - Manual Restoration: If you overwrite the implementation of a function with
jest.spyOn()
, you often need to manually restore the original implementation after your test(s) to prevent side effects in other tests. This is done using.mockRestore()
or.mockReset()
on the spied method.
Example:
const someObject = require('someModule');
jest.spyOn(someObject, 'someMethod'); // Spies on someMethod of someObject
Key Differences Summarized​
- Scope:
jest.mock()
is used for mocking entire modules, whereasjest.spyOn()
is for spying or mocking individual functions. - Default Behavior:
jest.mock()
mocks all module functions by default, whilejest.spyOn()
requires you to manually mock or alter function implementations. - Hoisting:
jest.mock()
calls are hoisted to the top, whilejest.spyOn()
is not, meaning the order ofjest.spyOn()
calls relative to other code matters. - Implementation Preservation:
jest.spyOn()
allows you to call through the real implementation and is more suited for adding behavior to existing functions, whilejest.mock()
is best when you want to avoid calling any real implementation from the start.
spyOn
​
Typically, jest.spyOn
is used for spying on methods of objects, which makes it straightforward for named exports or methods of classes. However, for default exports, especially when they are functions, the approach differs slightly because you can't directly spy on a module's default export in the same way you'd spy on object methods.
You would generally work around the limitation by adjusting how the module is imported and used. Since a direct application of jest.spyOn
on a default export isn't standard, let's explore a workaround that achieves a similar effect:
Using Module Import as Object for spying on Default Exports​
One way to apply jest.spyOn
on a function (including default exports) is by treating the import as an object. This requires changing the import statement slightly.
export default function () {
console.log("Doing something");
}
First, import the module itself (not the function) to get an object reference, and then use jest.spyOn
on the default
property of that module.
// Import the entire module as an object
import * as defaultFunctionModule from "./defaultFunction";
describe("defaultFunction tests", () => {
it("spies on the default exported function", () => {
// Spy on the default export
const spy = jest.spyOn(defaultFunctionModule, "default");
// Now, when defaultFunctionModule.default() is called, you can track it
defaultFunctionModule.default();
expect(spy).toHaveBeenCalled();
// Clean up
spy.mockRestore();
});
});
This approach allows you to track calls, arguments, and even mock the implementation while maintaining the original function's behavior unless you explicitly override it.