Mocking Global Objects in Jest: Techniques Only Pros Know About

Jest mocking techniques for global objects offer control in testing. Spy on functions, mock modules, manipulate time, and simulate APIs. Essential for creating reliable, isolated tests without external dependencies.

Mocking Global Objects in Jest: Techniques Only Pros Know About

Mocking global objects in Jest can be a real game-changer when you’re writing tests. It’s like having a secret weapon in your testing arsenal. I’ve been using Jest for years, and I gotta say, mastering these techniques has saved my bacon more times than I can count.

Let’s dive into the nitty-gritty of mocking global objects. First off, what are we talking about when we say “global objects”? Think window in browsers, or global in Node.js. These are the big kahunas that hold all sorts of important stuff your code might need.

Now, why would we want to mock these? Simple. Control. When you’re testing, you want to be the puppet master. You don’t want some random global state messing with your carefully crafted tests. Plus, it’s a great way to simulate different environments or scenarios without actually changing your code.

One of the coolest tricks I’ve learned is using Jest’s jest.spyOn() method. It’s like a ninja - stealthy and powerful. Here’s how you might use it to mock console.log:

const consoleSpy = jest.spyOn(console, 'log').mockImplementation();

// Your test code here

expect(consoleSpy).toHaveBeenCalledWith('Hello, World!');
consoleSpy.mockRestore();

This little snippet lets you check if console.log was called with the right arguments, without actually logging anything to the console. Neat, huh?

But what if you need to mock something that doesn’t exist yet? Jest has got you covered with jest.mock(). It’s like creating a whole new world for your tests. Check this out:

jest.mock('path/to/module', () => ({
  someFunction: jest.fn(() => 'mocked result'),
  someProperty: 'mocked value'
}));

This creates a mock module with exactly the bits you need for your test. It’s like building your own custom Lego set - only the pieces you want, nothing more.

Now, let’s talk about a personal favorite of mine: jest.useFakeTimers(). This bad boy lets you control time itself. Well, in your tests at least. It’s perfect for testing anything time-based without waiting around. Here’s a quick example:

jest.useFakeTimers();

test('setTimeout gets called', () => {
  const callback = jest.fn();
  setTimeout(callback, 1000);

  expect(callback).not.toBeCalled();
  jest.runAllTimers();
  expect(callback).toBeCalled();
});

In this snippet, we’re testing a setTimeout without actually waiting 1000 milliseconds. It’s like having a time machine for your tests!

But what about those pesky built-in objects like Date? Jest has a trick for that too. You can use jest.spyOn() to mock the entire Date object:

const mockDate = new Date('2023-05-14T12:00:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);

// Your test code here

jest.restoreAllMocks();

This lets you set the date to whatever you want for your tests. It’s super handy for testing date-dependent code without waiting for specific dates to roll around.

Now, let’s talk about mocking fetch. In the age of APIs, this is a crucial skill. Here’s how you might mock a fetch call:

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ data: 'mocked data' }),
  })
);

test('fetches data', async () => {
  const result = await fetchData();
  expect(result).toEqual({ data: 'mocked data' });
});

This lets you test your fetch calls without actually hitting any external APIs. It’s like having a fake server right in your test suite.

But what if you’re working with Node.js? No problem! Jest has got your back there too. You can mock entire modules using jest.mock(). Here’s an example with the fs module:

jest.mock('fs');

const fs = require('fs');
fs.readFileSync.mockReturnValue('mocked file contents');

test('reads file', () => {
  const contents = readFile('somefile.txt');
  expect(contents).toBe('mocked file contents');
});

This lets you test file operations without actually touching the file system. It’s like having a virtual file system just for your tests.

Now, let’s talk about a more advanced technique: mocking imports. This is super useful when you’re dealing with ES6 modules. Here’s how you might do it:

import * as myModule from './myModule';
jest.mock('./myModule', () => ({
  someFunction: jest.fn(),
  default: jest.fn(),
}));

test('uses imported function', () => {
  myModule.someFunction();
  expect(myModule.someFunction).toHaveBeenCalled();
});

This lets you mock specific functions from a module, giving you fine-grained control over your imports.

But what if you need to mock a whole bunch of things at once? Jest’s jest.doMock() is your friend here. It’s like jest.mock(), but on steroids. You can use it to mock multiple modules in one go:

jest.doMock('./config', () => ({ apiKey: 'fake-key' }));
jest.doMock('./api', () => ({ fetchData: jest.fn() }));

const { apiKey } = require('./config');
const { fetchData } = require('./api');

test('uses mocked modules', () => {
  expect(apiKey).toBe('fake-key');
  fetchData();
  expect(fetchData).toHaveBeenCalled();
});

This is super handy when you’re testing code that relies on multiple modules.

Now, let’s talk about a technique that’s saved my bacon more than once: mocking getters and setters. Jest lets you mock these too, which is awesome for testing computed properties. Here’s how:

const mock = {
  get prop() {
    return 'original';
  },
};

jest.spyOn(mock, 'prop', 'get').mockReturnValue('mocked');

test('mocks getter', () => {
  expect(mock.prop).toBe('mocked');
});

This lets you control what values your getters return, which is super useful for testing complex objects.

But what about those tricky global functions like setTimeout and setInterval? Jest has a whole suite of timer mocks that let you control these. Here’s a quick example:

jest.useFakeTimers();

test('advances timers', () => {
  const callback = jest.fn();
  setInterval(callback, 1000);

  jest.advanceTimersByTime(5000);
  expect(callback).toHaveBeenCalledTimes(5);
});

This lets you fast-forward through time in your tests, which is super handy for testing anything that relies on timers.

Now, let’s talk about something that’s often overlooked: mocking console.error. This is super useful for testing error handling without cluttering up your test output. Here’s how:

const errorSpy = jest.spyOn(console, 'error').mockImplementation();

test('handles errors', () => {
  someErrorProneFunction();
  expect(errorSpy).toHaveBeenCalledWith('Expected error message');
});

errorSpy.mockRestore();

This lets you check if errors are being logged correctly without actually seeing them in your test output.

But what about those pesky global event listeners? Jest has got you covered there too. You can mock addEventListener and removeEventListener to test your event handling code:

const listeners = {};
global.addEventListener = jest.fn((event, callback) => {
  listeners[event] = callback;
});
global.removeEventListener = jest.fn((event) => {
  delete listeners[event];
});

test('adds and removes event listeners', () => {
  const callback = jest.fn();
  addGlobalListener('click', callback);
  expect(global.addEventListener).toHaveBeenCalledWith('click', callback);

  removeGlobalListener('click');
  expect(global.removeEventListener).toHaveBeenCalledWith('click');
});

This lets you test your event handling code without actually triggering any events.

Now, let’s talk about something that’s bit me more than once: mocking Math.random(). This is crucial for any tests that involve randomness. Here’s how you might do it:

const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;

test('uses random number', () => {
  expect(getRandomNumber()).toBe(50);
});

This lets you control the “randomness” in your tests, making them predictable and repeatable.

But what about those pesky global variables? Jest lets you mock these too, which is super handy for testing code that relies on global state. Here’s a quick example:

global.myGlobalVar = 'original';

test('uses global variable', () => {
  global.myGlobalVar = 'mocked';
  expect(useGlobalVar()).toBe('mocked');
});

This lets you control the global state in your tests, which is crucial for testing code that relies on it.

Now, let’s talk about something that’s often overlooked: mocking process.env. This is super important for testing code that relies on environment variables. Here’s how you might do it:

const originalEnv = process.env;

beforeEach(() => {
  jest.resetModules();
  process.env = { ...originalEnv };
});

afterEach(() => {
  process.env = originalEnv;
});

test('uses environment variable', () => {
  process.env.MY_VAR = 'test value';
  const myModule = require('./myModule');
  expect(myModule.getEnvVar()).toBe('test value');
});

This lets you set environment variables for your tests without affecting your actual environment.

But what about those tricky import statements? Jest lets you mock these too, which is super handy for testing code that uses dynamic imports. Here’s a quick example:

jest.mock('./dynamicImport', () => ({
  __esModule: true,
  default: jest.fn(() => Promise.resolve({ data: 'mocked data' })),
}));

test('uses dynamic import', async () => {
  const result = await useDynamicImport();
  expect(result).toEqual({ data: 'mocked data' });
});

This lets you control what gets imported in your tests, which is crucial for testing code that uses dynamic imports.

Now, let’s wrap this up with a technique that’s saved my bacon more times than I can count: mocking window.location. This is super important for testing any code that deals with URLs or navigation. Here’s how you might do it:

const mockLocation = new URL('https://example.com');
delete window.location;
window.location = mockLocation;

test('uses window.location', () => {
  expect(getHostname()).toBe('example.com');
});

This lets you control the URL in your tests, which is crucial for testing any code that relies on the current URL.

And there you have it! A deep dive into the world of mocking global objects in Jest. These techniques have saved my bacon more times than I can count, and I hope they’ll do the same for you. Remember, the key to great testing is control, and with these techniques, you’re the master of your test domain. Happy testing!