Тестирование кода, использующего Dexie.js, в среде Node.js требует эмуляции IndexedDB, поскольку нативная реализация IndexedDB существует только в браузерах. Это создаёт специфический слой инфраструктуры тестирования: вместо реального браузерного хранилища используется in-memory или shim-реализация.
IndexedDB обладает рядом характеристик, которые усложняют тестирование:
Dexie.js абстрагирует эти сложности, но не устраняет зависимость от IndexedDB API, поэтому тестовая среда должна воспроизводить его поведение.
Основной подход — подключение реализации IndexedDB для Node.js через
fake-indexeddb или аналогичные библиотеки.
Типичный стек:
fake-indexeddbdexiejest или vitestИнициализация полифилла:
import 'fake-indexeddb/auto';
import Dexie fr om 'dexie';
После подключения fake-indexeddb глобальные объекты
indexedDB, IDBKeyRange,
DOMException становятся доступными, что позволяет Dexie
работать без изменений.
Для тестов обычно создаётся отдельный экземпляр базы данных:
import Dexie from 'dexie';
export function createTestDB() {
const db = new Dexie('TestDB');
db.version(1).stores({
users: '++id, name, age'
});
return db;
}
Ключевой принцип — каждый тест должен использовать собственный экземпляр базы или очищать данные между запусками.
Изоляция тестов критична, поскольку IndexedDB сохраняет состояние в рамках процесса.
Стандартный подход:
afterEach(async () => {
const db = createTestDB();
await db.delete();
});
Либо более контролируемый вариант:
beforeEach(async () => {
const db = createTestDB();
await db.open();
await db.users.clear();
});
Удаление базы предпочтительнее при сложных схемах с миграциями.
Пример теста добавления данных:
import { createTestDB } from './db';
test('добавление пользователя', async () => {
const db = createTestDB();
await db.open();
const id = await db.users.add({
name: 'Ivan',
age: 30
});
const user = await db.users.get(id);
expect(user.name).toBe('Ivan');
expect(user.age).toBe(30);
});
Dexie обеспечивает транзакционную целостность, поэтому чтение после записи можно тестировать без дополнительных синхронизаций.
Dexie поддерживает индексированные запросы через
where:
test('поиск по индексу', async () => {
const db = createTestDB();
await db.open();
await db.users.bulkAdd([
{ name: 'Anna', age: 25 },
{ name: 'Boris', age: 40 }
]);
const result = await db.users.wh ere('age').above(30).toArray();
expect(result.length).toBe(1);
expect(result[0].name).toBe('Boris');
});
Важно учитывать, что поведение индексов в fake-indexeddb
может отличаться по производительности, но логическая модель
совпадает.
Транзакции — ключевая часть Dexie. Их поведение особенно важно при сложных операциях:
test('транзакция записи', async () => {
const db = createTestDB();
await db.open();
await db.transaction('rw', db.users, async () => {
await db.users.add({ name: 'Test1', age: 20 });
await db.users.add({ name: 'Test2', age: 21 });
});
const count = await db.users.count();
expect(count).toBe(2);
});
Для проверки атомарности можно искусственно вызывать ошибки внутри транзакции:
test('откат транзакции при ошибке', async () => {
const db = createTestDB();
await db.open();
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);
});
Версионирование — одна из сильных сторон Dexie.
test('миграция версии базы', async () => {
const db = new Dexie('MigrateDB');
db.version(1).stores({
users: '++id, name'
});
db.version(2).stores({
users: '++id, name, age'
});
await db.open();
await db.users.add({ name: 'Old schema user', age: 50 });
const user = await db.users.get(1);
expect(user.age).toBe(50);
});
При тестировании миграций важно использовать чистую базу, иначе IndexedDB может сохранить старую версию схемы между тестами.
В некоторых случаях требуется изолировать бизнес-логику от реальной базы данных.
Подход — абстракция слоя доступа:
export class UserRepository {
constructor(db) {
this.db = db;
}
addUser(user) {
return this.db.users.add(user);
}
getAll() {
return this.db.users.toArray();
}
}
В тестах можно подменять db на мок:
const mockDb = {
users: {
add: jest.fn().mockResolvedValue(1),
toArray: jest.fn().mockResolvedValue([])
}
};
Такой подход позволяет тестировать бизнес-логику без IndexedDB.
При использовании jest важно явно подключать
polyfill:
import 'fake-indexeddb/auto';
При использовании vitest часто требуется настройка
среды:
// vitest.config.js
export default {
test: {
environment: 'node'
}
};
Иногда дополнительно подключается happy-dom, но для
Dexie он не обязателен.
Несовпадение поведения IndexedDB реализаций
Отсутствие реальных квот
Различия в транзакционной блокировке
Сохранение состояния между тестами
Dexie активно используется в асинхронных сценариях:
test('параллельные добавления', async () => {
const db = createTestDB();
await db.open();
await Promise.all([
db.users.add({ name: 'A' }),
db.users.add({ name: 'B' }),
db.users.add({ name: 'C' })
]);
const count = await db.users.count();
expect(count).toBe(3);
});
Такие тесты помогают выявлять проблемы с гонками, даже в упрощённой среде fake-indexeddb.
В более сложной архитектуре Dexie используется как persistence-слой, и тестируется через интеграцию:
Такой подход снижает зависимость тестов от деталей IndexedDB и делает их более устойчивыми к изменениям схемы и библиотек.