Skip to main content

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, whereas jest.spyOn() is for spying or mocking individual functions.
  • Default Behavior: jest.mock() mocks all module functions by default, while jest.spyOn() requires you to manually mock or alter function implementations.
  • Hoisting: jest.mock() calls are hoisted to the top, while jest.spyOn() is not, meaning the order of jest.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, while jest.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.

Given a Module: defaultFunction.js
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();
});
});
Maintaining Original Behavior

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.