The most surprising thing about Jest’s test environments is that jsdom isn’t actually a browser, and node isn’t actually Node.js.

Let’s see what that means in practice. Imagine you’re testing a component that manipulates the DOM. You’d naturally reach for jsdom.

// MyComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; // This import is crucial for jsdom-specific matchers

// Assume MyComponent renders a div with id="my-element"
import MyComponent from './MyComponent';

describe('MyComponent', () => {
  test('renders with the correct ID', () => {
    render(<MyComponent />);
    const element = screen.getByTestId('my-element'); // Using data-testid for clarity
    expect(element).toBeInTheDocument();
  });
});

If you run this with jsdom as your environment, Jest spins up a simulated DOM environment. This isn’t a real browser engine like Blink or Gecko, but a JavaScript implementation of the DOM API. It means you can use document.getElementById, addEventListener, and all the usual DOM manipulation methods. The @testing-library/jest-dom package provides helpful matchers like toBeInTheDocument that work specifically with this simulated DOM.

Now, consider a test for a backend utility function that makes HTTP requests. You’d likely configure your jest.config.js to use the node environment.

// api.test.js
// Assume this function uses node-fetch or the built-in fetch in Node.js v18+
import { fetchData } from './api';

describe('api', () => {
  test('fetches data successfully', async () => {
    // Mocking a successful API response
    const mockData = { message: 'success' };
    global.fetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve(mockData),
      })
    );

    const data = await fetchData('http://example.com/api');
    expect(data).toEqual(mockData);
    expect(fetch).toHaveBeenCalledWith('http://example.com/api');
  });
});

When Jest runs in the node environment, it executes your tests directly in a Node.js runtime. This means you have access to Node.js’s built-in modules (fs, http, etc.) and can use libraries like node-fetch if you’re on an older Node.js version. Crucially, global.fetch is available in newer Node.js versions, and Jest’s node environment provides it.

Here’s the mental model:

  • jsdom Environment: Jest launches a separate Node.js process, and within that process, it bootstraps jsdom. jsdom then provides a JavaScript implementation of the DOM, HTML, and a basic window object. Your tests interact with this simulated DOM. It’s like a browser, but it’s not. This is great for testing UI components and anything that relies on DOM APIs without the overhead of launching a full browser.
  • node Environment: Jest simply executes your tests directly within the Node.js runtime that Jest itself is running in. You get direct access to Node.js APIs, modules, and its event loop. This is ideal for testing backend logic, utility functions, CLI tools, or anything that doesn’t need a DOM.

You configure this in your jest.config.js:

// jest.config.js
module.exports = {
  // ... other Jest configurations
  testEnvironment: 'jsdom', // or 'node'
  // ...
};

If you’re not explicit, Jest defaults to jsdom for React projects and node for others. The key is understanding that jsdom is a simulation and node is the actual runtime. This distinction matters for performance, available APIs, and subtle behavioral differences.

What most people don’t realize is that the jsdom environment still runs within Node.js. So, you can actually access Node.js global variables like process or Buffer even when testEnvironment is set to 'jsdom'. Jest injects these Node.js globals into the jsdom environment for convenience, blurring the lines slightly but making it easier to write tests that might need both DOM and some Node.js utilities.

The next concept you’ll encounter is how Jest handles asynchronous operations in these different environments, especially concerning timers and event loops.

Want structured learning?

Take the full Jest course →