Использование jest и jest-environment-jsdom

Для работы с Jest необходимо добавить его в проект как зависимость разработки. Используется следующая команда:

npm install --save-dev jest

Если проект предполагает тестирование кода, связанного с DOM (например, взаимодействие с элементами страницы), необходимо подключить jest-environment-jsdom:

npm install --save-dev jest-environment-jsdom

После установки в package.json можно добавить скрипт для запуска тестов:

"scripts": {
  "test": "jest"
}

В конфигурации Jest можно явно указать среду выполнения для тестов, где требуется DOM:

"jest": {
  "testEnvironment": "jsdom"
}

Настройка среды jsdom

jest-environment-jsdom предоставляет имитацию браузерного окружения в Node.js. В среде jsdom доступны глобальные объекты, такие как window, document, navigator, что позволяет тестировать взаимодействие с DOM без запуска реального браузера.

Для изменения настроек jsdom можно использовать отдельный файл конфигурации. Например, создать jest.setup.js:

// Установка глобальных переменных
globalThis.window = window;
globalThis.document = document;
globalThis.navigator = {
  userAgent: 'node.js'
};

В конфигурации Jest подключается этот файл через опцию setupFiles:

"jest": {
  "testEnvironment": "jsdom",
  "setupFiles": ["<rootDir>/jest.setup.js"]
}

Создание тестов с использованием jsdom

Тестирование функций, работающих с DOM, выглядит следующим образом:

describe('DOM манипуляции', () => {
  beforeEach(() => {
    document.body.innerHTML = `
      <div id="container">
        <button id="btn">Click</button>
      </div>
    `;
  });

  test('кнопка существует', () => {
    const button = document.getElementById('btn');
    expect(button).not.toBeNull();
  });

  test('нажатие кнопки изменяет текст', () => {
    const button = document.getElementById('btn');
    button.addEventListener('click', () => {
      button.textContent = 'Clicked';
    });

    button.click();

    expect(button.textContent).toBe('Clicked');
  });
});

В этом примере используется метод beforeEach для инициализации DOM перед каждым тестом. Такой подход гарантирует, что тесты не будут зависеть друг от друга.

Мокирование функций и API браузера

В некоторых случаях нужно замокировать методы браузера, которые не реализованы в jsdom, например window.fetch:

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

test('fetch возвращает данные', async () => {
  const response = await fetch('/api/data');
  const data = await response.json();
  expect(data).toEqual({ data: 'test' });
});

Также возможно мокирование свойств DOM, таких как window.location:

delete window.location;
window.location = { href: 'http://localhost/' };

test('проверка изменения location', () => {
  window.location.href = 'http://example.com/';
  expect(window.location.href).toBe('http://example.com/');
});

Использование jest-environment-jsdom для асинхронных операций

jsdom поддерживает асинхронные события DOM, такие как setTimeout и Promise. Для тестирования асинхронного поведения удобно использовать async/await и методы Jest waitFor или таймеры:

test('асинхронное изменение DOM', async () => {
  const container = document.createElement('div');
  document.body.appendChild(container);

  setTimeout(() => {
    container.textContent = 'Done';
  }, 100);

  await new Promise(resolve => setTimeout(resolve, 150));

  expect(container.textContent).toBe('Done');
});

Такой подход позволяет эмулировать пользовательские действия и задержки, сохраняя тесты детерминированными.

Расширенные возможности конфигурации

Jest позволяет настраивать различные среды выполнения для отдельных тестов через директиву @jest-environment:

/**
 * @jest-environment jsdom
 */
test('тест с использованием jsdom', () => {
  document.body.innerHTML = '<p id="msg">Hello</p>';
  const msg = document.getElementById('msg');
  expect(msg.textContent).toBe('Hello');
});

Это удобно, когда проект содержит как серверные, так и клиентские тесты, требующие разных окружений.

Практические рекомендации

  • Разделять тесты по среде: серверные тесты оставлять в Node, а клиентские — в jsdom.
  • Использовать beforeEach и afterEach для очистки DOM и глобальных моков, чтобы избежать побочных эффектов.
  • Мокировать нестандартные API браузера, отсутствующие в jsdom.
  • Для сложных сценариев DOM можно подключать библиотеки, такие как @testing-library/dom, которые упрощают работу с элементами и событиями.

Такой подход обеспечивает стабильное тестирование фронтенд-кода в Node.js без необходимости запуска реального браузера.