В STOMP.js механизм переподключения является одной из ключевых частей устойчивой работы поверх нестабильных WebSocket-соединений. В реальных условиях сеть может разрываться, брокер может перезапускаться, соединение может закрываться по таймауту или из-за прокси. Поэтому логика переподключения становится не вспомогательной, а критически важной частью клиентской архитектуры.
В тестировании эта область требует особого внимания, поскольку поведение асинхронное, зависит от таймеров, состояния соединения и внешних событий WebSocket.
Основная сложность заключается в том, что переподключение в STOMP-клиентах обычно реализуется через цикл с задержками:
onWebSocketClosesetTimeoutactivate()Любая ошибка в этой цепочке приводит либо к бесконечным попыткам, либо к полной потере соединения без восстановления.
Для проверки логики переподключения требуется имитация поведения WebSocket. В большинстве тестовых сценариев используется подмена глобального WebSocket на мок-объект.
Базовая структура мока включает:
onclose,
onerror, onmessageПример модели:
class MockWebSocket {
constructor() {
this.ono pen = null;
this.oncl ose = null;
this.oner ror = null;
this.onmess age = null;
this.readyState = 1;
}
simulateOpen() {
this.readyState = 1;
this.onopen && this.onopen();
}
simulateClose(code = 1006) {
this.readyState = 3;
this.onclose && this.onclose({ code });
}
simulateMessage(data) {
this.onmessage && this.onmessage({ data });
}
}
Такая модель позволяет воспроизводить сценарии:
Типовой тест переподключения строится вокруг ожидания повторного
вызова activate() или создания нового WebSocket после
закрытия соединения.
Основная цель — убедиться, что клиент не останавливается после первого разрыва.
Логика теста:
closeПри использовании Jest важно контролировать таймеры:
jest.useFakeTimers();
test('reconnect after disconnect', () => {
const client = createStompClient();
client.activate();
mockSocket.simulateOpen();
mockSocket.simulateClose();
jest.advanceTimersByTime(5000);
expect(MockWebSocket.instances.length).toBeGreaterThan(1);
});
Ключевой момент — контроль времени. Без fake timers тесты становятся нестабильными и зависимыми от реального планировщика задач.
Во многих реализациях STOMP.js применяется стратегия exponential backoff:
Это предотвращает перегрузку брокера и сети.
Тестирование такой логики требует проверки не только факта переподключения, но и правильности интервалов.
Подход:
setTimeoutПример:
test('exponential backoff progression', () => {
const delays = [];
global.setTimeout = (fn, delay) => {
delays.push(delay);
fn();
};
const client = createStompClientWithBackoff();
client.activate();
mockSocket.simulateClose();
mockSocket.simulateClose();
mockSocket.simulateClose();
expect(delays[0]).toBe(500);
expect(delays[1]).toBe(1000);
expect(delays[2]).toBe(2000);
});
Важный аспект — контроль побочных эффектов. Реальные таймеры должны быть полностью изолированы.
Одной из самых сложных проблем является восстановление подписок. После переподключения клиент должен заново подписаться на топики.
Типичный поток:
subscribesubscribeОшибки в этом процессе приводят к:
Тестовая стратегия:
subscribetest('resubscribes after reconnect', () => {
const subscribeMock = jest.fn();
const client = createClient({
onConnect: () => {
client.subscribe('/topic/test', subscribeMock);
}
});
client.activate();
mockSocket.simulateOpen();
mockSocket.simulateClose();
jest.runOnlyPendingTimers();
mockSocket.simulateOpen();
expect(subscribeMock).toHaveBeenCalledTimes(2);
});
Здесь важно различать:
Реальные сети часто дают не один разрыв, а серию:
Такие сценарии приводят к состоянию гонки, если логика переподключения не защищена от параллельных запусков.
Основная проверка:
activate() вызовыtest('prevents parallel reconnect loops', () => {
const client = createClient();
client.activate();
mockSocket.simulateClose();
mockSocket.simulateClose();
mockSocket.simulateClose();
jest.advanceTimersByTime(10000);
expect(MockWebSocket.instances.length).toBeLessThanOrEqual(2);
});
Ключевой момент — защита состояния:
isReconnectingБолее реалистичный подход — использование фейкового STOMP-брокера. Он позволяет проверять не только reconnect, но и поведение подписок и доставки сообщений.
Эмуляция включает:
Сценарий:
Особое внимание уделяется сохранению подписок на стороне клиента, а не брокера.
Основная проблема тестирования reconnect-логики — недетерминированность.
Источники:
setTimeoutДля устранения нестабильности:
jest.useFakeTimers()Дополнительно применяются техники:
Date.now() при backoff-логикеОтдельный важный сценарий — корректное прекращение переподключений при явном уничтожении клиента.
После вызова deactivate():
test('stops reconnect after deactivate', () => {
const client = createClient();
client.activate();
mockSocket.simulateClose();
client.deactivate();
jest.runAllTimers();
expect(MockWebSocket.instances.length).toBe(1);
});
Критично проверять именно отсутствие дальнейших действий, а не только
факт вызова deactivate.
При длительной работе приложения переподключения могут приводить к накоплению:
Тестирование включает:
Подход часто дополняется инструментами профилирования памяти, но базовые проверки возможны и на уровне unit-тестов через мок-объекты.
Некоторые брокеры могут:
Для таких случаев тесты моделируют:
onerror без oncloseЦель — убедиться, что клиент не входит в бесконечный цикл без паузы и не блокирует event loop.