IndexedDB — это браузерное API, которое работает асинхронно, зависит от окружения и недоступно в Node.js по умолчанию. Это создаёт проблему при написании unit-тестов для кода, использующего Dexie.js: тесты становятся либо интеграционными (в реальном браузере), либо требуют эмуляции базы данных.
Библиотека fake-indexeddb решает эту задачу, предоставляя полную in-memory реализацию IndexedDB API, совместимую с большинством сценариев использования Dexie. Это позволяет запускать тесты в Node.js без браузера, сохраняя поведение, максимально близкое к реальному IndexedDB.
Ключевая идея применения fake-indexeddb — подмена глобального
indexedDB в тестовом окружении, чтобы Dexie продолжал
работать без изменений в коде приложения.
Для начала необходимо установить зависимости тестирования:
Пример установки:
npm install dexie fake-indexeddb
Если используется Jest:
npm install --save-dev jest
Dexie по умолчанию ожидает наличие глобального
indexedDB, IDBKeyRange,
IDBTransaction. В Node.js их нет, поэтому fake-indexeddb
должен быть подключён до создания базы данных.
Типовой файл настройки тестового окружения:
import "fake-indexeddb/auto";
Модуль auto автоматически:
indexedDBIDBKeyRangeПосле этого 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();
});
Такой подход гарантирует, что:
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 работает в памяти, поэтому:
Это полезно для unit-тестов, но не подходит для performance-тестирования.
Некоторые особенности браузерного IndexedDB могут отличаться:
После завершения процесса Node.js:
Для Jest рекомендуется вынести подключение fake-indexeddb в setup файл:
// jest.setup.js
import "fake-indexeddb/auto";
И подключить в конфигурации:
{
"setupFiles": ["./jest.setup.js"]
}
В Vitest аналогично:
import "fake-indexeddb/auto";
или через setupFiles:
export default {
test: {
setupFiles: "./test/setup.js",
},
};
Ошибка проявляется как:
DatabaseClosedErrorРешение: всегда вызывать db.close().
Dexie требует строгого управления версиями:
Если не удалять базу:
Надёжный подход включает:
db.close() в afterEachdb.delete() для очистки состоянияbeforeEach для полной инициализации
схемыfake-indexeddb/auto один раз на уровне
setupfake-indexeddb позволяет воспроизводить реальные сценарии:
Это делает его полноценным инструментом для unit-тестирования логики, связанной с Dexie, без необходимости браузера.