Асинхронные хранилища браузера создают специфическую проблему в тестировании: состояние сохраняется между прогонками тестов, если его явно не сбрасывать. Библиотека localForage, использующая IndexedDB, WebSQL или localStorage в зависимости от окружения, усиливает эту проблему из-за кэширования соединений и отложенных операций записи.
Изоляция тестов в контексте localForage требует дисциплины в управлении жизненным циклом хранилища: инициализация, очистка, возможное пересоздание драйверов и контроль конкурентных операций.
localForage работает как абстракция над несколькими механизмами хранения:
Ключевая особенность — асинхронный API, даже если backend синхронный. Это приводит к тому, что операции записи и очистки не гарантируют мгновенного эффекта без ожидания Promise.
Дополнительно:
Основной способ сброса состояния:
import localforage from "localforage";
await localforage.clear();
Метод clear() удаляет все ключи текущего экземпляра
хранилища. Важно учитывать:
При использовании нескольких экземпляров:
const storeA = localforage.createInstance({ name: "A" });
const storeB = localforage.createInstance({ name: "B" });
await storeA.clear();
Очистка одного instance не затрагивает другие, если они используют разные namespace.
В сценариях, где требуется полное удаление базы IndexedDB:
await localforage.dropInstance({
name: "app-db",
storeName: "cache"
});
Этот метод:
Ограничение: поддерживается не всеми драйверами одинаково, особенно memoryStorage.
Наиболее распространённый подход — использование
beforeEach / afterEach.
import localforage from "localforage";
beforeEach(async () => {
await localforage.clear();
});
afterEach(async () => {
await localforage.clear();
});
Размещение очистки в обоих хуках снижает риск утечки состояния при прерывании теста.
import { beforeEach } from "vitest";
import localforage from "localforage";
beforeEach(async () => {
await localforage.clear();
});
Дополнительно важно учитывать, что Vitest может выполнять тесты параллельно, что увеличивает вероятность гонок.
beforeEach(async function () {
await localforage.clear();
});
При асинхронных тестах необходимо использовать done или
return Promise.
Асинхронность IndexedDB приводит к ситуации:
setItemclearРезультат — нестабильное состояние.
afterEach(async () => {
await localforage.clear();
await new Promise(resolve => setTimeout(resolve, 0));
});
Микрозадержка позволяет завершить pending transactions.
Оптимальная стратегия — замена драйвера:
import localforage from "localforage";
import memoryDriver from "localforage-memoryStorageDriver";
await localforage.defineDriver(memoryDriver);
await localforage.setDriver(memoryDriver._driver);
Преимущества:
Очистка в этом случае становится тривиальной:
await localforage.clear();
Иногда требуется не очистка, а полное пересоздание состояния:
let store;
beforeEach(async () => {
store = localforage.createInstance({
name: "test-db"
});
await store.clear();
});
Подход полезен при тестировании различных конфигураций driver.
В редких случаях localForage не полностью освобождает IndexedDB соединения. Тогда применяется ручное удаление базы:
const deleteDB = indexedDB.deleteDatabase("test-db");
await new Promise((resolve, reject) => {
deleteDB.onsucc ess = resolve;
deleteDB.oner ror = reject;
});
Это гарантирует полное удаление состояния вне зависимости от localForage cache.
Подход, снижающий необходимость очистки:
const store = localforage.createInstance({
name: `test-db-${Date.now()}`
});
Каждый тест получает уникальную базу, что исключает пересечения.
Недостаток — накопление мусора в IndexedDB при длительных прогонах.
При включённом параллелизме:
beforeEach не гарантируетсяРешение:
export async function safeClear(store = localforage) {
try {
await store.clear();
} catch (e) {
await new Promise(resolve => setTimeout(resolve, 50));
await store.clear();
}
}
Подход учитывает нестабильность IndexedDB транзакций.
При проверке схем данных важно гарантировать отсутствие старых ключей:
beforeEach(async () => {
await localforage.dropInstance({
name: "app-db"
});
await localforage.createInstance({
name: "app-db"
});
});
Это позволяет тестировать начальную миграцию без влияния предыдущих структур.
Иногда требуется не просто очистка, а проверка:
const keys = await localforage.keys();
for (const key of keys) {
await localforage.removeItem(key);
}
Такой подход полезен при отладке, когда clear() ведёт
себя нестабильно из-за драйвера.
В CI системах:
Рекомендуемые меры:
await clear()beforeEach(() => {
localforage.clear(); // ошибка
});
Результат — гонки между тестами.
const store = localforage.createInstance({ name: "A" });
await localforage.clear(); // не влияет на store A
IndexedDB всегда асинхронен, даже если API кажется простым.
Наиболее устойчивый подход включает комбинацию:
clear() в хукахdropInstanceТакая комбинация устраняет большинство проблем, связанных с утечкой состояния между тестами и нестабильностью IndexedDB транзакций.