Тестирование кода, использующего Dexie.js, требует имитации IndexedDB, поскольку в Node.js среде браузерное API отсутствует. Основная задача тестовой инфраструктуры — воспроизвести поведение IndexedDB максимально близко к реальному браузеру, сохранив детерминированность и скорость выполнения тестов.
В экосистеме тестирования чаще всего применяются два подхода:
fake-indexeddb для эмуляции IndexedDB в
Node.jsБазовая установка зависимостей:
npm install dexie
npm install -D jest vitest fake-indexeddb
Дополнительно часто подключается dexie-export-import для
тестирования миграций и экспорта данных.
Dexie при создании базы обращается к глобальному
indexedDB. В Node.js его необходимо подменить.
Для Jest:
import 'fake-indexeddb/auto';
import Dexie fr om 'dexie';
class TestDB extends Dexie {
constructor() {
super('TestDB');
this.version(1).stores({
users: '++id,name,age'
});
}
}
export const db = new TestDB();
Подключение fake-indexeddb/auto автоматически создаёт
глобальные реализации:
Этого достаточно для большинства сценариев тестирования CRUD-операций Dexie.
Jest требует явной настройки среды, если тестируется IndexedDB логика.
Файл jest.config.js:
module.exports = {
testEnvironment: 'node',
setupFiles: ['fake-indexeddb/auto']
};
При необходимости изоляции состояния базы добавляется teardown логика:
import { db } from './db';
afterEach(async () => {
await db.delete();
await db.open();
});
Такой подход предотвращает утечки состояния между тестами и гарантирует чистую базу перед каждым кейсом.
Dexie.js предоставляет Promise-based API, что упрощает тестирование через async/await.
Пример теста добавления и чтения:
import { db } from './db';
test('добавление пользователя', async () => {
const id = await db.users.add({ name: 'Alex', age: 25 });
const user = await db.users.get(id);
expect(user.name).toBe('Alex');
expect(user.age).toBe(25);
});
Обновление записи:
test('обновление пользователя', async () => {
const id = await db.users.add({ name: 'Anna', age: 30 });
await db.users.update(id, { age: 31 });
const updated = await db.users.get(id);
expect(updated.age).toBe(31);
});
Удаление:
test('удаление пользователя', async () => {
const id = await db.users.add({ name: 'John', age: 40 });
await db.users.delete(id);
const result = await db.users.get(id);
expect(result).toBeUndefined();
});
Изоляция состояния критична, поскольку IndexedDB сохраняет данные в рамках базы даже между тестами.
Используется один из подходов:
afterEach(async () => {
await db.delete();
await db.open();
});
afterEach(async () => {
await db.users.clear();
});
Первый вариант предпочтителен при тестировании миграций и схемы, второй — при тестировании бизнес-логики.
Dexie поддерживает атомарные транзакции, которые также подлежат тестированию.
test('транзакция добавления данных', async () => {
await db.transaction('rw', db.users, async () => {
await db.users.add({ name: 'Tom', age: 22 });
await db.users.add({ name: 'Jerry', age: 20 });
});
const count = await db.users.count();
expect(count).toBe(2);
});
Проверка отката:
test('откат транзакции при ошибке', async () => {
try {
await db.transaction('rw', db.users, async () => {
await db.users.add({ name: 'A' });
throw new Error('fail');
});
} catch {}
const count = await db.users.count();
expect(count).toBe(0);
});
Vitest имеет более современную архитектуру и встроенную поддержку ESM, что делает интеграцию с Dexie более прямолинейной.
Установка:
npm install -D vitest fake-indexeddb
Конфигурация vitest.config.js:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
setupFiles: ['fake-indexeddb/auto']
}
});
Vitest использует Vite runtime, поэтому важно учитывать:
Пример теста:
import { describe, it, expect, beforeEach } from 'vitest';
import { db } from './db';
beforeEach(async () => {
await db.delete();
await db.open();
});
describe('Dexie users', () => {
it('создание записи', async () => {
const id = await db.users.add({ name: 'Mike' });
const user = await db.users.get(id);
expect(user.name).toBe('Mike');
});
});
Dexie поддерживает версионирование базы данных через
version().stores(). Тестирование миграций требует
последовательного пересоздания базы.
class DB extends Dexie {
constructor() {
super('MigrationDB');
this.version(1).stores({
users: '++id,name'
});
this.version(2).stores({
users: '++id,name,age'
}).upgrade(tx => {
return tx.table('users').toCollection().modify(user => {
user.age = 0;
});
});
}
}
Тест миграции:
test('миграция добавляет поле age', async () => {
const db1 = new DB();
await db1.version(1).upgrade();
await db1.users.add({ name: 'Test' });
await db1.close();
const db2 = new DB();
const user = await db2.users.toArray();
expect(user[0].age).toBe(0);
});
fake-indexeddb покрывает большинство сценариев, но не
полностью повторяет поведение браузерного IndexedDB.
Ключевые отличия:
Из-за этого критические участки логики иногда требуют дополнительной проверки в реальном браузере.
В некоторых тестах Dexie не требуется как реальная база, достаточно имитации.
Пример мокирования:
jest.mock('./db', () => ({
db: {
users: {
add: jest.fn(),
get: jest.fn(),
update: jest.fn()
}
}
}));
Такой подход используется для unit-тестирования сервисного слоя, отделённого от хранения данных.
Dexie поддерживает live queries через liveQuery и
reactive extensions.
import { liveQuery } from 'dexie';
test('реактивный запрос обновляется', async () => {
const observable = liveQuery(() => db.users.toArray());
const results = [];
const subscription = observable.subscribe({
next: value => results.push(value)
});
await db.users.add({ name: 'React' });
await new Promise(r => setTimeout(r, 50));
expect(results.length).toBeGreaterThan(0);
subscription.unsubscribe();
});
В тестах важно учитывать асинхронные задержки реактивных потоков.
Dexie полностью асинхронен, поэтому синхронные assertions приводят к нестабильным тестам.
Рекомендуемые практики:
await.then() в тестахwaitFor (в случае Vitest + Testing
Library)Пример ожидания:
await expect(db.users.add({ name: 'Async' }))
.resolves
.toBeDefined();
Dexie позволяет использовать индексы, которые также тестируются через queries.
test('поиск по индексу', async () => {
await db.users.bulkAdd([
{ name: 'Alice', age: 20 },
{ name: 'Bob', age: 30 }
]);
const result = await db.users.wh ere('age').above(25).toArray();
expect(result.length).toBe(1);
expect(result[0].name).toBe('Bob');
});
test('bulkAdd добавляет несколько записей', async () => {
await db.users.bulkAdd([
{ name: 'A' },
{ name: 'B' },
{ name: 'C' }
]);
const count = await db.users.count();
expect(count).toBe(3);
});
При параллельном запуске тестов важно избегать конфликтов имен баз.
Подходы:
const db = new Dexie(`TestDB_${Date.now()}`);
beforeEachtest('ошибка при невалидной записи', async () => {
await expect(
db.users.add(null)
).rejects.toThrow();
});
Dexie оборачивает ошибки IndexedDB в собственные исключения, которые
также можно проверять через instanceof.
import { Dexie } from 'dexie';
try {
await db.users.add(null);
} catch (e) {
expect(e instanceof Dexie.DexieError).toBe(true);
}