STOMP.js реализует протокол обмена сообщениями поверх WebSocket или других транспортов, что делает его асинхронным и событийно-ориентированным по своей природе. Это накладывает специфические требования на модульное тестирование: необходимо учитывать отложенные события, состояние соединения и взаимодействие с внешним брокером сообщений.
Ключевая сложность тестирования заключается в том, что STOMP-клиент почти всегда зависит от внешней среды — WebSocket-соединения и брокера (RabbitMQ, ActiveMQ, Apollo и др.). Поэтому модульные тесты должны изолировать логику клиента от реального сетевого уровня.
Основные задачи тестирования STOMP.js-компонентов:
Для полноценного модульного тестирования WebSocket-слой заменяется заглушкой (mock). Это позволяет имитировать поведение брокера без реального соединения.
Типовой подход заключается в создании фейкового WebSocket:
class MockWebSocket {
constructor(url) {
this.url = url;
this.readyState = 1;
this.sentMessages = [];
this.listeners = {};
}
send(data) {
this.sentMessages.push(data);
}
close() {
this.readyState = 3;
this.emit('close');
}
on(event, handler) {
this.listeners[event] = handler;
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event](data);
}
}
}
Такой подход позволяет полностью контролировать поведение транспорта и проверять только бизнес-логику STOMP-клиента.
Процесс подключения в STOMP включает отправку CONNECT и
получение CONNECTED. Проверка этого сценария является
базовой частью модульного тестирования.
Типовой тест проверяет:
Пример тестового сценария:
test('should send CONNECT frame on connect()', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
expect(ws.sentMessages[0]).toContain('CONNECT');
});
Дополнительно проверяется реакция на подтверждение соединения:
test('should set connected state on CONNECTED frame', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
ws.emit('message', 'CONNECTED\nversion:1.2\n\n\u0000');
expect(client.isConnected).toBe(true);
});
Подписка является центральной частью работы STOMP-клиента. Модульные тесты должны гарантировать:
Пример теста подписки:
test('should send SUBSCRIBE frame with correct destination', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
client.subscribe('/queue/test', () => {});
const lastMessage = ws.sentMessages.pop();
expect(lastMessage).toContain('SUBSCRIBE');
expect(lastMessage).toContain('/queue/test');
});
Также важно проверять обработку входящих сообщений:
test('should invoke callback on MESSAGE frame', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
let received = null;
client.connect();
client.subscribe('/queue/test', (msg) => {
received = msg;
});
ws.emit('message', 'MESSAGE\ndestination:/queue/test\n\nHello\u0000');
expect(received).toBe('Hello');
});
Отправка сообщений требует проверки корректности формирования STOMP-фрейма, включая заголовки и тело сообщения.
test('should send MESSAGE via SEND frame', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
client.send('/queue/test', {}, 'payload');
const frame = ws.sentMessages.find(m => m.includes('SEND'));
expect(frame).toContain('/queue/test');
expect(frame).toContain('payload');
});
Особое внимание уделяется сериализации payload, особенно если используется JSON:
client.send('/queue/test', {}, JSON.stringify({ a: 1 }));
В тестах проверяется, что сериализация не нарушает формат STOMP-фрейма.
STOMP-клиент обязан корректно обрабатывать разрыв соединения и ошибки транспорта. Модульные тесты проверяют:
close;test('should handle connection close', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
ws.close();
expect(client.isConnected).toBe(false);
});
Также проверяется механизм повторного подключения:
test('should attempt reconnect on failure', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws, { reconnectDelay: 1000 });
client.connect();
ws.emit('close');
expect(client.reconnectAttempts).toBeGreaterThan(0);
});
STOMP является событийно-ориентированным протоколом, поэтому критически важно проверять порядок обработки событий.
Проверяется, что:
test('should preserve message order', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
const received = [];
client.connect();
client.subscribe('/queue/test', (msg) => received.push(msg));
ws.emit('message', 'MESSAGE\ndestination:/queue/test\n\n1\u0000');
ws.emit('message', 'MESSAGE\ndestination:/queue/test\n\n2\u0000');
ws.emit('message', 'MESSAGE\ndestination:/queue/test\n\n3\u0000');
expect(received).toEqual(['1', '2', '3']);
});
При тестировании переподключений и heartbeat часто требуется управление таймерами. Используются fake timers.
jest.useFakeTimers();
test('should send heartbeat periodically', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws, { heartbeat: 5000 });
client.connect();
jest.advanceTimersByTime(5000);
expect(ws.sentMessages.some(m => m.includes('PING'))).toBe(true);
});
Асинхронные операции требуют ожидания событий:
test('should resolve after connection established', async () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
const promise = client.connectAsync();
ws.emit('message', 'CONNECTED\nversion:1.2\n\n\u0000');
await expect(promise).resolves.toBeUndefined();
});
Для STOMP.js-клиентов применяются следующие подходы:
Типовая структура:
tests/
stomp/
connection.test.js
subscribe.test.js
send.test.js
reconnect.test.js
mocks/
MockWebSocket.js
Некоторые операции STOMP-клиента должны быть безопасны при повторном вызове:
test('should not create multiple connections', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
client.connect();
expect(ws.sentMessages.filter(m => m.includes('CONNECT')).length).toBe(1);
});
При разрыве соединения необходимо убедиться, что подписки очищаются, иначе возможны утечки памяти и дублирование сообщений при переподключении.
test('should clear subscriptions on disconnect', () => {
const ws = new MockWebSocket();
const client = new StompClient(ws);
client.connect();
client.subscribe('/queue/test', () => {});
ws.close();
expect(client.subscriptions.size).toBe(0);
});