Интеграционное тестирование с реальным IndexedDB

Интеграционные тесты, работающие с реальным IndexedDB, проверяют поведение кода в условиях, максимально приближенных к браузерной среде. Библиотека localForage опирается на IndexedDB как на основной backend в современных браузерах, поэтому корректная проверка взаимодействия с ним требует отказа от упрощённых моков и использования полноценного хранилища.

IndexedDB отличается асинхронной моделью, транзакционностью и особенностями реализации в разных браузерах. Именно эти свойства делают интеграционные тесты критически важными: они выявляют проблемы, которые невозможно воспроизвести в unit-тестах с заглушками.

Ключевые особенности поведения IndexedDB, влияющие на тестирование:

  • асинхронные операции чтения и записи;
  • строгая модель транзакций;
  • задержки и непредсказуемый порядок выполнения микротасков;
  • различия реализации в Chromium, Firefox и WebKit;
  • ограничения среды выполнения (например, Node.js без браузерного контекста).

localForage скрывает эти сложности, предоставляя единый Promise-based API, однако тестовая среда всё равно должна учитывать реальную природу хранилища.


Подготовка тестовой среды с IndexedDB

Интеграционные тесты с IndexedDB выполняются в браузероподобной среде. На практике используется несколько подходов:

Браузерные раннеры

Наиболее достоверный вариант — запуск тестов в реальном браузере через инструменты:

  • Playwright
  • Puppeteer
  • Cypress

Такая среда обеспечивает:

  • полноценный IndexedDB;
  • реальное поведение event loop;
  • корректную работу localForage без эмуляции.

Node.js с jsdom

jsdom частично эмулирует браузер, но IndexedDB в нём отсутствует или реализован ограниченно. Это делает его непригодным для полноценного тестирования localForage без дополнительных полифилов.

Полифилы IndexedDB

Для Node.js-среды используются реализации:

  • fake-indexeddb
  • indexeddbshim

Они позволяют запускать тесты в CI, но важно учитывать:

  • поведение может отличаться от реального браузера;
  • возможны расхождения в транзакциях;
  • нет полной идентичности производительности и блокировок.

Конфигурация localForage для тестов

Перед запуском тестов важно обеспечить изолированную конфигурацию хранилища.

Типичная настройка:

  • отдельный name базы;
  • уникальный storeName для каждого тестового набора;
  • использование IndexedDB driver в приоритете.

Ключевой принцип — изоляция данных между тестами.

Основные параметры:

  • driver: localForage.INDEXEDDB;
  • name: тестовая база;
  • storeName: уникальный namespace;
  • отключение fallback на WebSQL/LocalStorage при необходимости детерминированности.

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

Стабильность интеграционных тестов напрямую зависит от корректной очистки IndexedDB.

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

Самый надёжный способ — удаление базы целиком:

  • indexedDB.deleteDatabase(name)

Этот подход гарантирует отсутствие остаточных данных и блокировок транзакций.

Очистка через localForage API

Менее радикальный способ:

  • localForage.clear()

Он очищает store, но:

  • не удаляет структуру базы;
  • может сохранять метаданные в некоторых реализациях;
  • быстрее, но менее изолирован.

Временные артефакты транзакций

IndexedDB может удерживать соединения даже после очистки. Поэтому часто требуется:

  • ожидание завершения всех транзакций;
  • принудительное закрытие соединений в тестовом окружении;
  • последовательный запуск тестов при нестабильности среды.

Асинхронная природа тестов localForage

Все операции localForage возвращают Promise, что требует строгого контроля асинхронности.

Базовые операции

  • setItem
  • getItem
  • removeItem
  • clear

Каждая операция:

  • выполняется в отдельной транзакции IndexedDB;
  • может завершиться в следующем тике event loop;
  • не гарантирует синхронность даже при последовательных вызовах.

Типичная ошибка тестирования

Ошибкой является предположение о моментальном сохранении:

localForage.setItem('key', 'value');
expect(localForage.getItem('key')).toBe('value');

Такой код приводит к гонкам, поскольку setItem не завершён.

Корректная форма требует ожидания Promise:

await localForage.setItem('key', 'value');
const result = await localForage.getItem('key');

Гонки и нестабильность в IndexedDB

IndexedDB допускает конкурентные операции, но их порядок не всегда очевиден.

Проблемные сценарии

  • параллельные записи одного ключа;
  • одновременные clear() и setItem();
  • чтение во время миграции структуры;
  • повторные инициализации localForage.

Типичный race condition

Если два теста используют одну базу:

  • один тест вызывает clear();
  • второй выполняет setItem() одновременно;
  • итоговое состояние становится недетерминированным.

Решение — изоляция или последовательный запуск.


Стратегии изоляции тестов

Уникальные базы на тест

Каждый тест создаёт собственную базу:

  • уникальное имя через UUID;
  • очистка после завершения;
  • минимизация конфликтов транзакций.

Изоляция через beforeEach / afterEach

Типовой паттерн:

  • beforeEach: инициализация localForage;
  • afterEach: удаление базы.

Полное разделение контекстов

В Playwright или Puppeteer можно:

  • запускать отдельную страницу на тест;
  • изолировать IndexedDB storage context;
  • предотвращать утечки состояния между тестами.

Миграции данных и тестирование схем

localForage не предоставляет строгой схемы данных, но приложения часто реализуют версионирование вручную.

Проблемы миграций

  • изменение структуры объекта;
  • добавление новых ключей;
  • преобразование старых значений;
  • несовместимость старых записей.

Тестирование миграций

Интеграционные тесты должны проверять:

  • корректную загрузку старых данных;
  • автоматическую трансформацию структуры;
  • отсутствие потери информации;
  • идемпотентность миграций.

Типичный сценарий:

  1. записать данные старой версии;
  2. изменить версию схемы;
  3. перезапустить инициализацию;
  4. проверить трансформацию.

Поведение IndexedDB в CI-средах

CI окружения часто становятся источником нестабильности тестов.

Основные проблемы

  • отсутствие полноценного IndexedDB;
  • ограниченные файловые системы;
  • параллельные процессы;
  • замедление event loop.

Решения

  • использование headless Chrome с Playwright;
  • фиксация версии браузера;
  • отключение параллельного запуска тестов;
  • применение fake-indexeddb только для unit-уровня.

Производительность интеграционных тестов

IndexedDB может быть медленнее in-memory хранилищ, особенно при:

  • больших объёмах данных;
  • частых транзакциях;
  • последовательных операциях записи.

Оптимизации

  • минимизация данных в тестах;
  • группировка операций;
  • избегание ненужных clear() между шагами;
  • повторное использование открытой базы в одном тесте.

Отладка поведения localForage

При работе с IndexedDB важно уметь диагностировать состояние базы.

Подходы к отладке

  • DevTools Application → IndexedDB;
  • логирование всех операций wrapper-слоем;
  • перехват Promise-цепочек;
  • инспекция транзакций через браузерные инструменты.

Частые проблемы

  • “зависшие” транзакции;
  • несоответствие между записью и чтением;
  • потеря данных при очистке;
  • неожиданные fallback-драйверы.

Контроль fallback-драйверов localForage

localForage может переключаться между драйверами:

  • IndexedDB;
  • WebSQL (устаревший);
  • localStorage.

В тестах важно фиксировать поведение:

  • принудительное указание INDEXEDDB;
  • проверка выбранного драйвера через getDriver() или внутренние API;
  • запрет fallback при тестировании критичных сценариев.

Поведение при конкурентных инициализациях

Частая ошибка — многократный вызов config() и повторная инициализация localForage.

Это может привести к:

  • созданию нескольких connection pools;
  • конфликтам транзакций;
  • непредсказуемому поведению setItem/getItem.

Тесты должны проверять:

  • идемпотентность инициализации;
  • устойчивость к повторным вызовам;
  • корректное переиспользование базы.

Стабилизация асинхронных ожиданий

В IndexedDB нет синхронных гарантий завершения операций, поэтому тесты требуют явного ожидания.

Типовые ошибки:

  • отсутствие await;
  • использование таймеров вместо Promise;
  • попытка синхронной проверки состояния.

Корректный подход:

  • строго Promise-based ожидания;
  • последовательные цепочки операций;
  • контроль завершения транзакций перед проверками.

Надёжность интеграционных тестов

Ключевая цель тестирования localForage с IndexedDB — не просто проверка API, а выявление:

  • гонок;
  • утечек состояния;
  • неконсистентности данных;
  • ошибок транзакционного уровня.

Надёжные тесты всегда учитывают реальное поведение браузерного хранилища, а не его упрощённые модели.