Тестирование асинхронных операций

Idb-keyval — это простая и легковесная обёртка над IndexedDB, позволяющая работать с ключ-значение хранилищем в браузере через удобный асинхронный API. Все операции с данными возвращают промисы, что делает библиотеку удобной для интеграции с современными асинхронными потоками данных.

Основные функции библиотеки:

  • get(key) — получение значения по ключу.
  • set(key, value) — сохранение значения по ключу.
  • del(key) — удаление значения по ключу.
  • clear() — очистка всего хранилища.
  • keys() — получение списка всех ключей.

Пример использования базовых операций:

import { set, get, del, clear, keys } from 'idb-keyval';

// Сохранение значения
await set('username', 'Alice');

// Получение значения
const username = await get('username');

// Удаление значения
await del('username');

// Очистка хранилища
await clear();

// Получение всех ключей
const allKeys = await keys();

Все функции возвращают промисы, что делает возможным использование их с async/await или .then().


Асинхронность и тестирование

Все операции Idb-keyval выполняются асинхронно, поэтому тестирование требует правильного управления временем ожидания результатов. Простые синхронные проверки не подходят — необходимо работать с промисами.

Пример проверки сохранения данных:

import { set, get } from 'idb-keyval';

async function testSetAndGet() {
    await set('key1', 'value1');
    const result = await get('key1');
    if (result !== 'value1') {
        throw new Error('Значение не совпадает');
    }
}

Ключевой момент: тест должен ожидать завершения операции, иначе результат может быть неопределённым.


Использование Jest для тестирования асинхронных функций

При работе с Jest функции с промисами должны возвращать промис или использовать async/await. Пример:

import { set, get, del } from 'idb-keyval';

test('сохранение и получение значения', async () => {
    await set('testKey', 'testValue');
    const value = await get('testKey');
    expect(value).toBe('testValue');
});

test('удаление значения', async () => {
    await set('testKey', 'testValue');
    await del('testKey');
    const value = await get('testKey');
    expect(value).toBeUndefined();
});

Если не использовать async/await или не возвращать промис, Jest завершит тест до окончания операции IndexedDB, и тест может давать ложноположительные или ложноотрицательные результаты.


Работа с ошибками

Idb-keyval автоматически обрабатывает большинство ошибок IndexedDB, но при тестировании важно учитывать возможные сбои:

import { set, get } from 'idb-keyval';

test('ошибка при чтении несуществующего ключа', async () => {
    try {
        const value = await get('nonExistentKey');
        expect(value).toBeUndefined();
    } catch (err) {
        throw new Error('Произошла непредвиденная ошибка: ' + err.message);
    }
});

Асинхронное тестирование должно предусматривать обработку исключений через try/catch, чтобы тест корректно фиксировал ошибки, не прерывая выполнение.


Параллельные операции

Idb-keyval поддерживает параллельные операции, однако при тестировании важно убедиться, что промисы разрешены в правильном порядке, если это важно для логики приложения.

Пример параллельного сохранения:

import { set, get } from 'idb-keyval';

async function testParallelSets() {
    const promises = [
        set('key1', 'value1'),
        set('key2', 'value2'),
        set('key3', 'value3')
    ];

    await Promise.all(promises);

    const values = await Promise.all([
        get('key1'),
        get('key2'),
        get('key3')
    ]);

    if (values[0] !== 'value1' || values[1] !== 'value2' || values[2] !== 'value3') {
        throw new Error('Ошибка при параллельной записи');
    }
}

Promise.all гарантирует, что все операции завершены перед проверкой результата.


Очистка и изоляция тестов

Для корректного тестирования важно очищать хранилище между тестами, чтобы состояние одного теста не влияло на другой:

import { clear } from 'idb-keyval';

beforeEach(async () => {
    await clear();
});

Использование beforeEach или afterEach обеспечивает полную изоляцию асинхронных операций и предотвращает непредсказуемое поведение тестов.


Интеграция с асинхронными потоками данных

Idb-keyval удобно использовать с асинхронными генераторами, потоками RxJS или другими асинхронными библиотеками. Пример с асинхронным генератором:

import { set, get } from 'idb-keyval';

async function* generateKeys() {
    for (let i = 1; i <= 3; i++) {
        await set(`key${i}`, `value${i}`);
        yield await get(`key${i}`);
    }
}

(async () => {
    for await (const value of generateKeys()) {
        console.log(value);
    }
})();

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


Рекомендации по тестированию

  • Использовать async/await для всех операций Idb-keyval.
  • Гарантировать очистку хранилища между тестами.
  • Проверять все возможные состояния: существующий ключ, отсутствующий ключ, параллельные операции.
  • Обрабатывать ошибки через try/catch для корректной фиксации исключений.
  • Использовать Promise.all для параллельных операций с последующей проверкой результатов.

Idb-keyval упрощает работу с IndexedDB и делает асинхронные операции более предсказуемыми, но строгая организация тестов необходима для корректной проверки работы приложений в реальных условиях.