Typing для custom matchers

Jasmine, как популярный фреймворк для тестирования JavaScript, предоставляет широкие возможности для создания тестов с использованием предсказуемых и понятных синтаксических конструкций. Одной из таких возможностей является создание custom matchers — кастомных матчеров, которые позволяют расширить стандартные возможности для проверки утверждений в тестах. Важно, чтобы в процессе разработки кастомных матчеров не возникало проблем с типизацией, особенно в таких языках, как TypeScript, где типы играют ключевую роль в поддержании надежности и читаемости кода.

Что такое кастомные матчеры в Jasmine?

Кастомные матчеры в Jasmine позволяют добавлять собственные правила для сравнения объектов в тестах. Это особенно полезно, когда стандартных матчеров недостаточно для проверки сложных или специфических условий.

expect(value).toBeGreaterThan(10);
expect(value).toMatch(/^abc/);

Тем не менее, в некоторых случаях стандартных матчеров может не хватить, и тогда на помощь приходят кастомные. Например, для проверки сложных структур данных или значений, которые зависят от определённых условий.

Типизация кастомных матчеров в TypeScript

Для правильной типизации кастомных матчеров в Jasmine важно обеспечить соответствие между тем, что ожидается в тестах, и реальной реализацией матчеров. В Jasmine, как правило, матчеры представляют собой функции, которые проверяют определенные условия и возвращают объект с результатом.

Чтобы использовать Jasmine с TypeScript, необходимо учитывать несколько важных моментов:

  1. Типы для кастомных матчеров: Jasmine предоставляет базовый интерфейс Matcher для кастомных матчеров. Каждый кастомный мачтер должен быть объектом, реализующим этот интерфейс.

  2. Типизация функции addMatchers: Для добавления кастомных матчеров в Jasmine используется метод addMatchers, который принимает объект с определениями матчеров. Важно, чтобы типы для этих матчеров были указаны правильно.

Реализация кастомного матчера

Пример простого кастомного матчера

Рассмотрим пример создания простого кастомного матчера, который проверяет, является ли переданное значение чётным числом.

declare global {
  namespace jasmine {
    interface Matchers<T> {
      toBeEven(): boolean;
    }
  }
}

beforeAll(() => {
  jasmine.addMatchers({
    toBeEven: () => {
      return {
        compare: (actual: number) => {
          const result = { pass: actual % 2 === 0 };
          result.message = `Expected ${actual} to be an even number`;
          return result;
        }
      };
    }
  });
});

В этом примере:

  • Мы расширяем интерфейс Matchers для добавления нового метода toBeEven.
  • В методе compare осуществляется проверка числа на четность. Тип actual в этом случае — это число.

Важным моментом здесь является расширение глобального интерфейса Matchers, которое позволяет TypeScript корректно типизировать кастомный мачтер и предоставить автодополнение в редакторе кода.

Типизация возвращаемого объекта в кастомном мачтере

Каждый кастомный мачтер должен возвращать объект с двумя обязательными полями:

  • pass — булевое значение, которое указывает, прошёл ли тест.
  • message — строка с сообщением, которое будет выведено в случае неудачи.

Правильная типизация этих полей позволяет избежать ошибок в коде и улучшить качество тестов.

Типизация параметров кастомных матчеров

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

declare global {
  namespace jasmine {
    interface Matchers<T> {
      toBeObjectWithProperty(key: string): boolean;
    }
  }
}

beforeAll(() => {
  jasmine.addMatchers({
    toBeObjectWithProperty: () => {
      return {
        compare: (actual: any, key: string) => {
          const result = { pass: actual && actual.hasOwnProperty(key) };
          result.message = `Expected object to have property '${key}'`;
          return result;
        }
      };
    }
  });
});

В данном примере:

  • Параметр actual типизирован как any, поскольку мы работаем с объектами.
  • Параметр key — это строка, которая представляет имя свойства.

Интеграция кастомных матчеров с TypeScript

Когда кастомные матчеры интегрируются в проект с использованием TypeScript, важно правильно настраивать типы для глобальных объектов Jasmine, чтобы не возникало проблем с типизацией во время разработки. Для этого часто используется расширение глобальных интерфейсов или модулей.

import 'jasmine';

declare global {
  namespace jasmine {
    interface Matchers<T> {
      toHaveKey(key: string): boolean;
    }
  }
}

beforeEach(() => {
  jasmine.addMatchers({
    toHaveKey: () => {
      return {
        compare: (actual: object, key: string) => {
          const result = { pass: actual.hasOwnProperty(key) };
          result.message = `Expected object to have the key '${key}'`;
          return result;
        }
      };
    }
  });
});

Типы для кастомных матчеров должны быть объявлены в глобальном контексте, что позволяет TypeScript правильно понимать, какие методы доступны для использования в процессе тестирования.

Утверждения и ошибки

Типизация кастомных матчеров также помогает минимизировать вероятность ошибок. При неверно написанных матчах TypeScript выдаст ошибку на этапе компиляции, что позволяет оперативно исправить проблему до запуска тестов. Это улучшает опыт разработки и сокращает время на отладку.

Работа с асинхронными матчерами

Для асинхронных операций, например, проверки промисов, кастомные матчеры в Jasmine могут быть дополнены асинхронной логикой. Важно корректно типизировать такие матчеры, чтобы избежать проблем с возвращаемыми значениями и их типами.

declare global {
  namespace jasmine {
    interface Matchers<T> {
      toBeResolved(): Promise<boolean>;
    }
  }
}

beforeEach(() => {
  jasmine.addMatchers({
    toBeResolved: () => {
      return {
        compare: async (actual: Promise<any>) => {
          try {
            await actual;
            return { pass: true, message: 'Promise resolved successfully' };
          } catch (e) {
            return { pass: false, message: 'Promise did not resolve' };
          }
        }
      };
    }
  });
});

В данном примере:

  • Метод compare асинхронно ожидает завершение промиса.
  • Типизация возвращаемого значения промиса и самого теста теперь правильно настроена.

Заключение

Правильная типизация кастомных матчеров в Jasmine, особенно при использовании TypeScript, позволяет создавать более надежные и предсказуемые тесты. Важно учитывать типы параметров и возвращаемых значений в кастомных матчерах, чтобы использовать возможности TypeScript на полную мощность, обеспечивая тем самым стабильность кода.