Интеграционное тестирование

Роль интеграционного тестирования в WebSocket-архитектуре

Интеграционное тестирование в контексте STOMP.js применяется для проверки взаимодействия между клиентским кодом и брокером сообщений через WebSocket-протокол. В отличие от модульных тестов, где изолируются отдельные функции или классы, здесь проверяется полноценный поток сообщений: подключение, подписка, отправка, получение и обработка сообщений.

Основная цель — подтвердить корректность работы всей цепочки:

  • клиент STOMP.js
  • транспорт WebSocket
  • брокер сообщений (RabbitMQ, ActiveMQ, Spring STOMP Broker и др.)
  • серверная логика маршрутизации сообщений

Ключевая особенность STOMP — текстовый протокол поверх WebSocket, что упрощает тестирование на уровне сообщений, но требует строгого контроля состояния соединения.


Подготовка тестового окружения

Интеграционные тесты STOMP.js невозможно реализовать без реального или эмулированного брокера сообщений. Обычно используется один из подходов:

Локальный брокер сообщений

Часто поднимается контейнеризированная среда:

  • RabbitMQ с STOMP plugin
  • ActiveMQ
  • Spring Boot WebSocket STOMP endpoint

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

Тестовый WebSocket endpoint

Для JavaScript-стека часто создаётся минимальный сервер:

  • Node.js + ws
  • @stomp/stompjs server-side bridge
  • или Spring WebSocket controller

Он имитирует реальные сценарии обмена сообщениями и маршрутизации.


Архитектура интеграционного теста

Типовой тест STOMP.js включает следующие этапы:

  1. Инициализация клиента STOMP
  2. Подключение к WebSocket endpoint
  3. Подписка на топик
  4. Отправка сообщения в брокер
  5. Ожидание ответа
  6. Проверка полученного результата
  7. Завершение соединения

Каждый этап требует строгого контроля асинхронности, так как STOMP работает на событийной модели.


Инициализация STOMP-клиента в тестах

В интеграционных тестах чаще используется @stomp/stompjs:

import { Client } from '@stomp/stompjs';

function createClient(url) {
  return new Client({
    brokerURL: url,
    reconnectDelay: 0,
    heartbeatIncoming: 0,
    heartbeatOutgoing: 0,
    debug: () => {}
  });
}

Отключение heartbeat и reconnect важно для предсказуемости тестов.


Подключение и контроль состояния соединения

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

function connectClient(client) {
  return new Promise((resolve, reject) => {
    client.onConn ect = () => resolve();
    client.onStompEr ror = (frame) => reject(frame);
    client.activate();
  });
}

Контроль ошибок подключения критичен, поскольку сбой на этом этапе делает остальные проверки бессмысленными.


Тестирование подписки на топики

Подписка проверяет корректность маршрутизации сообщений через брокер:

function subscribe(client, destination) {
  return new Promise((resolve) => {
    client.subscribe(destination, (message) => {
      resolve(message.body);
    });
  });
}

В интеграционных сценариях важно учитывать:

  • корректность destination (topic/queue)
  • сериализацию payload
  • задержки доставки сообщений

Отправка сообщений через STOMP

Отправка сообщений является ключевым элементом тестируемого потока:

function sendMessage(client, destination, payload) {
  client.publish({
    destination,
    body: JSON.stringify(payload)
  });
}

В интеграционных тестах проверяется не только факт отправки, но и то, что брокер правильно маршрутизирует сообщение.


Сценарий полного интеграционного теста

Полный тест объединяет все стадии:

test('STOMP integration flow', async () => {
  const client = createClient('ws://localhost:8080/ws');

  await connectClient(client);

  const messagePromise = subscribe(client, '/topic/events');

  sendMessage(client, '/app/events', { type: 'PING' });

  const response = await messagePromise;

  expect(JSON.parse(response)).toEqual({ type: 'PONG' });

  client.deactivate();
});

Важным аспектом является синхронизация подписки и отправки: подписка должна быть активна до публикации сообщения.


Работа с задержками и асинхронностью

STOMP-сообщения не гарантируют мгновенную доставку. В интеграционных тестах применяются стратегии:

Таймауты ожидания

function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), ms)
    )
  ]);
}

Буферизация сообщений

При множественных событиях используется накопление:

function subscribeBuffer(client, destination, count) {
  const messages = [];
  return new Promise((resolve) => {
    client.subscribe(destination, (msg) => {
      messages.push(msg.body);
      if (messages.length === count) {
        resolve(messages);
      }
    });
  });
}

Проверка маршрутизации через брокер

Интеграционные тесты должны учитывать разделение каналов:

  • /app/* — входящие сообщения к серверу
  • /topic/* — broadcast сообщения
  • /queue/* — point-to-point доставка

Корректная маршрутизация проверяется через несколько клиентов:

const clientA = createClient(url);
const clientB = createClient(url);

await connectClient(clientA);
await connectClient(clientB);

const received = subscribe(clientB, '/topic/shared');

sendMessage(clientA, '/app/shared', { value: 1 });

Тестирование восстановления соединения

Интеграционные сценарии часто включают проверку устойчивости:

client.onWebSocketCl ose = async () => {
  await new Promise(r => setTimeout(r, 1000));
  client.activate();
};

Проверяется:

  • восстановление подписок
  • повторная доставка сообщений
  • сохранение состояния клиента

Эмуляция брокера сообщений

При отсутствии реального брокера используется мок-сервер:

import { Server } from 'ws';

const wss = new Server({ port: 8081 });

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    ws.send(data.toString());
  });
});

Этот подход ограничен, но подходит для проверки логики клиента без полноценного STOMP-брокера.


Использование Spring WebSocket STOMP в тестах

В Java-экосистеме часто применяется Spring Boot:

  • @EnableWebSocketMessageBroker
  • SimpMessagingTemplate

Интеграционные тесты JavaScript-клиента подключаются к Spring endpoint:

  • /ws WebSocket endpoint
  • /topic broker relay
  • /app message mapping

Это обеспечивает реалистичную проверку production-архитектуры.


Логирование и диагностика тестов

При нестабильных тестах критично включать debug:

client = new Client({
  debug: (msg) => console.log('[STOMP]', msg)
});

Анализируются:

  • CONNECT frames
  • SUBSCRIBE frames
  • MESSAGE frames
  • ERROR frames

Типовые ошибки интеграционного тестирования

Часто встречающиеся проблемы:

  • подписка после отправки сообщения
  • отсутствие heartbeat при реальном брокере
  • некорректный destination mapping
  • утечка соединений между тестами
  • конкуренция между асинхронными обработчиками

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


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

Для стабильности применяется:

  • создание нового STOMP клиента на каждый тест
  • уникальные топики (/topic/test-${uuid})
  • очистка подписок после завершения
  • отдельный namespace на брокере

Поведение при множественных подписках

STOMP.js позволяет несколько подписок на один клиент:

client.subscribe('/topic/a', handlerA);
client.subscribe('/topic/b', handlerB);

В тестах важно проверять отсутствие перекрёстной доставки сообщений между каналами.


Нагрузочные аспекты интеграционного тестирования

При увеличении количества сообщений тесты начинают выявлять:

  • ограничения брокера по throughput
  • задержки сериализации JSON
  • блокировки event loop в Node.js
  • деградацию WebSocket соединений

Для анализа используется батч-отправка сообщений и измерение времени доставки.


Проверка сериализации данных

STOMP работает с текстовыми payload, поэтому важна проверка:

  • JSON.stringify перед отправкой
  • JSON.parse после получения
  • корректность encoding (UTF-8)

Ошибки сериализации часто проявляются только на интеграционном уровне.


Интеграция с CI/CD пайплайном

Интеграционные тесты STOMP.js обычно выполняются:

  • в Docker окружении
  • с поднятым брокером как service dependency
  • с ограничением по времени выполнения

Типовая структура:

  • поднятие брокера
  • запуск тестов
  • уничтожение окружения

Подход к стабильности тестов

Ключевые принципы:

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

Диагностика нестабильных интеграционных сценариев

При флаппинг-тестах анализируется:

  • порядок событий onConnect / subscribe / publish
  • race conditions между подпиской и публикацией
  • задержки брокера
  • повторные доставки сообщений

Используются временные маркеры и логирование frame-level событий STOMP протокола.