Зачем контрактное тестирование?

В микросервисах сервисы разрабатываются и развёртываются независимо. Без контрактного тестирования вы полагаетесь на интеграционные тесты (медленные и хрупкие), документацию (может быть устаревшей) или удачу.

Контрактное тестирование проверяет, что сервисы могут корректно взаимодействовать, не требуя одновременной работы всех сервисов.

Как работает Pact

Pact использует consumer-driven подход: потребитель определяет ожидания, Pact генерирует контракт, провайдер его верифицирует.

Написание тестов потребителя

const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
const { like, string, integer } = MatchersV3;

const provider = new PactV3({
  consumer: 'OrderService',
  provider: 'UserService',
});

describe('Контракт UserService', () => {
  test('получение пользователя по ID', async () => {
    await provider
      .given('пользователь с ID 123 существует')
      .uponReceiving('запрос пользователя 123')
      .withRequest({ method: 'GET', path: '/users/123' })
      .willRespondWith({
        status: 200,
        body: {
          id: integer(123),
          name: string('Alice'),
          email: string('alice@example.com'),
        },
      });

    await provider.executeTest(async (mockServer) => {
      const userClient = new UserClient(mockServer.url);
      const user = await userClient.getUser(123);
      expect(user.id).toBe(123);
    });
  });
});

Написание тестов провайдера

const { Verifier } = require('@pact-foundation/pact');

describe('Верификация провайдера UserService', () => {
  test('верифицирует все контракты', async () => {
    const verifier = new Verifier({
      providerBaseUrl: 'http://localhost:3001',
      pactBrokerUrl: 'https://your-broker.pact.io',
      provider: 'UserService',
      stateHandlers: {
        'пользователь с ID 123 существует': async () => {
          await db.users.create({ id: 123, name: 'Alice', email: 'alice@example.com' });
        },
      },
    });
    await verifier.verifyProvider();
  });
});

Матчеры Pact

Матчеры делают контракты гибкими: like() для типа, string() для строк, integer() для целых, eachLike() для массивов, regex() для паттернов.

Pact Broker

Центральный репозиторий контрактов. Команда can-i-deploy отвечает: «безопасно ли развернуть эту версию?»

Упражнение: Реализация контрактного тестирования

Задание 1: Контрактные тесты потребителя

Напишите тесты для OrderService, потребляющего ProductService API с 4 взаимодействиями.

Задание 2: Верификация провайдера

Напишите тесты провайдера с state handler-ами для каждого состояния “given”.

Задание 3: Интеграция с Pact Broker

Настройте локальный Pact Broker, публикуйте контракты, настройте верификацию, используйте can-i-deploy.

Задание 4: Обнаружение ломающих изменений

Внесите ломающее изменение, запустите верификацию, задокументируйте ошибку, исправьте.

Задание 5: Интеграция в CI Pipeline

Спроектируйте pipeline: Consumer CI публикует контракты, Provider CI верифицирует.

Результаты

  1. Код тестов потребителя минимум с 4 взаимодействиями.
  2. Код верификации провайдера с state handler-ами.
  3. Настройка Pact Broker.
  4. Документ дизайна CI pipeline.
  5. Демонстрация обнаружения ломающих изменений.