Использование fake-indexeddb для unit-тестов

IndexedDB — это браузерное API, которое работает асинхронно, зависит от окружения и недоступно в Node.js по умолчанию. Это создаёт проблему при написании unit-тестов для кода, использующего Dexie.js: тесты становятся либо интеграционными (в реальном браузере), либо требуют эмуляции базы данных.

Библиотека fake-indexeddb решает эту задачу, предоставляя полную in-memory реализацию IndexedDB API, совместимую с большинством сценариев использования Dexie. Это позволяет запускать тесты в Node.js без браузера, сохраняя поведение, максимально близкое к реальному IndexedDB.

Ключевая идея применения fake-indexeddb — подмена глобального indexedDB в тестовом окружении, чтобы Dexie продолжал работать без изменений в коде приложения.


Базовая установка тестового окружения

Для начала необходимо установить зависимости тестирования:

  • Dexie
  • fake-indexeddb
  • тестовый раннер (Jest, Vitest или Mocha)

Пример установки:

npm install dexie fake-indexeddb

Если используется Jest:

npm install --save-dev jest

Подключение fake-indexeddb в Node.js

Dexie по умолчанию ожидает наличие глобального indexedDB, IDBKeyRange, IDBTransaction. В Node.js их нет, поэтому fake-indexeddb должен быть подключён до создания базы данных.

Типовой файл настройки тестового окружения:

import "fake-indexeddb/auto";

Модуль auto автоматически:

  • создаёт глобальный indexedDB
  • подменяет IDBKeyRange
  • добавляет необходимые интерфейсы

После этого Dexie начинает работать так, будто он запущен в браузере.


Создание тестовой базы Dexie

Dexie инициализируется как обычно, без специальных условий для тестов.

import Dexie fr om "dexie";

export const db = new Dexie("TestDB");

db.version(1).stores({
  users: "++id,name,age",
});

Важно: одна и та же схема должна использоваться во всех тестах, иначе возможны конфликты версий.


Очистка состояния между тестами

Одной из ключевых проблем in-memory IndexedDB является сохранение состояния между тестами. fake-indexeddb не очищается автоматически.

Полное удаление базы

import { deleteDatabase } from "fake-indexeddb";

beforeEach(async () => {
  await db.delete();
});

Однако более надёжный вариант — явное закрытие соединений:

beforeEach(async () => {
  db.close();
  await db.delete();
});

Изоляция тестов

Каждый тест должен работать с «чистой» базой. Это предотвращает утечки данных и флейки.

beforeEach(async () => {
  db.close();
  await db.delete();

  await db.open();
});

Такой подход гарантирует, что:

  • schema пересоздаётся заново
  • транзакции не пересекаются
  • состояние полностью детерминировано

Пример unit-теста с Dexie

import Dexie from "dexie";
import "fake-indexeddb/auto";

let db;

beforeEach(async () => {
  db = new Dexie("UsersDB");

  db.version(1).stores({
    users: "++id,name,age",
  });

  await db.open();
});

afterEach(async () => {
  db.close();
  await db.delete();
});

test("добавление пользователя", async () => {
  await db.users.add({ name: "Alex", age: 30 });

  const count = await db.users.count();
  expect(count).toBe(1);
});

Тестирование сложных операций

Массовая вставка

test("bulkAdd пользователей", async () => {
  await db.users.bulkAdd([
    { name: "A", age: 20 },
    { name: "B", age: 25 },
    { name: "C", age: 35 },
  ]);

  const all = await db.users.toArray();
  expect(all.length).toBe(3);
});

Индексированные запросы

test("поиск по индексу age", async () => {
  await db.users.bulkAdd([
    { name: "A", age: 20 },
    { name: "B", age: 30 },
    { name: "C", age: 30 },
  ]);

  const result = await db.users.wh ere("age").equals(30).toArray();

  expect(result.length).toBe(2);
});

Тестирование транзакций

Dexie транзакции полностью поддерживаются fake-indexeddb, что позволяет проверять атомарность операций.

test("транзакция add + update", async () => {
  await db.transaction("rw", db.users, async () => {
    const id = await db.users.add({ name: "John", age: 40 });

    await db.users.update(id, { age: 41 });
  });

  const user = await db.users.get(1);
  expect(user.age).toBe(41);
});

Проверка откатов транзакций

test("rollback транзакции при ошибке", async () => {
  try {
    await db.transaction("rw", db.users, async () => {
      await db.users.add({ name: "X", age: 10 });
      throw new Error("fail");
    });
  } catch (e) {}

  const count = await db.users.count();
  expect(count).toBe(0);
});

Особенности fake-indexeddb

Несмотря на высокую совместимость, есть нюансы:

1. Производительность

fake-indexeddb работает в памяти, поэтому:

  • быстрее IndexedDB в браузере
  • не отражает реальные задержки I/O

Это полезно для unit-тестов, но не подходит для performance-тестирования.


2. Ограничения поведения

Некоторые особенности браузерного IndexedDB могут отличаться:

  • тонкости блокировок
  • редкие edge-case ошибки
  • различия в реализации event loop

3. Отсутствие persistence

После завершения процесса Node.js:

  • данные полностью исчезают
  • нет возможности проверить реальное хранение между сессиями

Использование с Jest

Для Jest рекомендуется вынести подключение fake-indexeddb в setup файл:

// jest.setup.js
import "fake-indexeddb/auto";

И подключить в конфигурации:

{
  "setupFiles": ["./jest.setup.js"]
}

Использование с Vitest

В Vitest аналогично:

import "fake-indexeddb/auto";

или через setupFiles:

export default {
  test: {
    setupFiles: "./test/setup.js",
  },
};

Частые ошибки при тестировании Dexie с fake-indexeddb

1. Повторное создание базы без закрытия

Ошибка проявляется как:

  • DatabaseClosedError
  • зависание промисов

Решение: всегда вызывать db.close().


2. Конфликт версий схемы

Dexie требует строгого управления версиями:

  • нельзя менять stores без увеличения version
  • fake-indexeddb не компенсирует ошибки схемы

3. Утечки между тестами

Если не удалять базу:

  • данные сохраняются между тестами
  • появляются флейки

Стратегия построения стабильных тестов

Надёжный подход включает:

  • создание новой Dexie-инстанции на каждый тест
  • обязательный db.close() в afterEach
  • db.delete() для очистки состояния
  • использование beforeEach для полной инициализации схемы
  • импорт fake-indexeddb/auto один раз на уровне setup

Моделирование сложных сценариев

fake-indexeddb позволяет воспроизводить реальные сценарии:

  • конкурирующие транзакции
  • частичные обновления
  • массовые операции записи
  • сложные запросы через индексы

Это делает его полноценным инструментом для unit-тестирования логики, связанной с Dexie, без необходимости браузера.