Ошибки в STOMP-клиентах не являются побочным эффектом — они являются частью нормального жизненного цикла соединения. В STOMP.js ошибки возникают на разных уровнях: транспортном, протокольном и прикладном. Для корректного тестирования необходимо разделять эти уровни, поскольку поведение приложения зависит от типа сбоя, а не от факта ошибки как таковой.
Тестирование ошибок в STOMP.js строится вокруг нескольких ключевых сценариев:
Каждый сценарий требует отдельной стратегии эмуляции и проверки.
STOMP.js предоставляет несколько уровней обработки ошибок:
onStompErroronWebSocketErroronDisconnectТипичная конфигурация клиента:
import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
reconnectDelay: 0,
debug: (msg) => console.log(msg),
onStompError: (frame) => {
console.error('STOMP error:', frame.headers['message']);
},
onWebSocketError: (event) => {
console.error('WebSocket error:', event);
},
onDisconnect: (frame) => {
console.warn('Disconnected:', frame);
}
});
При тестировании важно не просто проверить факт вызова callback, но и
гарантировать корректность входных данных frame,
event и состояния клиента.
Основной способ тестирования ошибок — замена WebSocket на контролируемый mock.
class MockWebSocket {
constructor(url) {
this.url = url;
this.readyState = 0;
setTimeout(() => this.onopen?.({}), 10);
}
send(data) {
this.lastSent = data;
}
close() {
this.readyState = 3;
this.onclose?.({ code: 1006, reason: 'abnormal' });
}
simulateError() {
this.onerror?.(new Event('error'));
}
}
Подмена в STOMP.js:
global.WebSocket = MockWebSocket;
Далее можно тестировать поведение клиента при различных сбоях:
onWebSocketErrorCLOSEDreconnectDelay = 0STOMP протокол определяет отдельный тип frame — ERROR.
Он приходит от брокера и не связан напрямую с WebSocket.
Пример тестовой симуляции:
function simulateStompError(client) {
const frame = {
command: 'ERROR',
headers: {
message: 'Authentication failed'
},
body: 'Bad credentials'
};
client.onStompError(frame);
}
В тестах важно проверять:
headers.messageframe.bodyОшибка аутентификации является частным случаем
ERROR frame, но требует отдельного сценария, поскольку
влияет на весь жизненный цикл подключения.
test('auth error handling', () => {
const client = createClient();
let errorReceived = null;
client.onStompEr ror = (frame) => {
errorReceived = frame;
};
simulateStompError(client);
expect(errorReceived.headers.message).toBe('Authentication failed');
});
Критически важно проверять не только факт ошибки, но и то, что клиент не пытается автоматически переподключиться при фатальной причине.
Разрыв соединения может быть инициирован как сервером, так и сетью.
Сценарий тестирования:
test('unexpected disconnect', () => {
const client = createClient();
let disconnected = false;
client.onDisconn ect = () => {
disconnected = true;
};
client.activate();
client.webSocket.close();
expect(disconnected).toBe(true);
});
Особое внимание уделяется состояниям:
CONNECTINGOPENDISCONNECTINGCLOSEDНеправильный переход состояний является частой причиной утечек подписок.
STOMP.js поддерживает автоматическое переподключение через
reconnectDelay.
const client = new Client({
reconnectDelay: 2000
});
Тест должен проверять:
activateМокирование таймеров:
jest.useFakeTimers();
client.activate();
client.webSocket.simulateError();
jest.advanceTimersByTime(2000);
expect(client.connected).toBe(true);
Ошибки могут возникать не только при соединении, но и при подписке.
client.subscribe('/topic/test', (message) => {
throw new Error('Handler failure');
});
Хотя STOMP.js не обязан обрабатывать исключения внутри callback, тестирование должно учитывать:
Тест:
test('subscription handler error isolation', () => {
const client = createClient();
client.subscribe('/topic/test', () => {
throw new Error('fail');
});
expect(() => {
client._deliverMessage({
body: 'data'
});
}).not.toThrow();
});
Сетевые таймауты не всегда явно поддерживаются WebSocket API, поэтому их моделирование осуществляется через искусственные задержки.
class SlowWebSocket extends MockWebSocket {
send() {
setTimeout(() => {
this.onerror?.(new Event('timeout'));
}, 5000);
}
}
Проверяются следующие аспекты:
Реальные системы часто генерируют каскад ошибок: разрыв соединения + ошибка брокера + сбой подписки.
Тестирование должно учитывать:
test('error storm handling', () => {
const client = createClient();
let errorCount = 0;
client.onWebSocketEr ror = () => errorCount++;
client.onStompEr ror = () => errorCount++;
client.webSocket.simulateError();
simulateStompError(client);
expect(errorCount).toBe(2);
});
Одним из ключевых аспектов тестирования является контроль состояния клиента после ошибки.
Проверяются:
connected === falsedeactivate()test('state cleanup after error', () => {
const client = createClient();
client.activate();
client.webSocket.simulateError();
expect(client.connected).toBe(false);
expect(client.subscriptions.size).toBe(0);
});
В тестах часто требуется не только проверка результата, но и анализ логов STOMP.js.
const logs = [];
const client = new Client({
debug: (msg) => logs.push(msg)
});
Проверяется:
Ошибки STOMP.js должны рассматриваться как часть контракта API:
Тестирование строится вокруг проверки соблюдения этого контракта, а не только факта вызова callback-функций.