Asymmetric equality matchers

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


Основные концепции

Ассиметричные матчеры — это объекты, реализующие метод asymmetricMatch(actual), который возвращает true, если переданное значение соответствует критериям, и false в противном случае. Такой подход позволяет создавать гибкие проверки, которые не зависят от полного совпадения структуры объекта или массива.

Все ассиметричные матчеры реализуют стандартный интерфейс Jasmine и могут использоваться в методах expect:

expect(actual).toEqual(jasmine.any(Number));

Здесь jasmine.any(Number) является ассиметричным матчером, который проверяет, что actual является числом, независимо от конкретного значения.


Встроенные ассиметричные матчеры

  1. jasmine.any(constructor) Проверяет тип значения, используя конструктор. Подходит для проверки объектов, массивов и встроенных типов.

    expect({name: "Alex", age: 30}).toEqual({
      name: jasmine.any(String),
      age: jasmine.any(Number)
    });
  2. jasmine.anything() Соответствует любому значению, кроме null и undefined.

    expect(getValue()).toEqual(jasmine.anything());
  3. jasmine.objectContaining(obj) Проверяет, что объект содержит указанные свойства с соответствующими значениями. Полезно, когда объект имеет дополнительные динамические свойства.

    const user = {id: 1, name: "Alice", role: "admin"};
    expect(user).toEqual(jasmine.objectContaining({name: "Alice"}));
  4. jasmine.arrayContaining(array) Проверяет, что массив содержит указанные элементы, без учёта порядка и дополнительных элементов.

    expect([1, 2, 3, 4]).toEqual(jasmine.arrayContaining([2, 4]));
  5. jasmine.stringMatching(pattern) Проверяет, что строка соответствует регулярному выражению или содержит указанный подстроковый паттерн.

    expect("hello world").toEqual(jasmine.stringMatching(/^hello/));
  6. jasmine.objectContaining и jasmine.arrayContaining в сочетании Ассиметричные матчеры можно комбинировать для проверки сложных структур данных:

    const response = {
      users: [
        {id: 1, name: "Alice"},
        {id: 2, name: "Bob"}
      ],
      meta: {total: 2}
    };
    
    expect(response).toEqual(jasmine.objectContaining({
      users: jasmine.arrayContaining([
        jasmine.objectContaining({name: "Alice"})
      ])
    }));

Пользовательские ассиметричные матчеры

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

class GreaterThan {
  constructor(expected) {
    this.expected = expected;
  }

  asymmetricMatch(actual) {
    return actual > this.expected;
  }

  toString() {
    return `<GreaterThan ${this.expected}>`;
  }

  jasmineToString() {
    return this.toString();
  }
}

expect(10).toEqual(new GreaterThan(5)); // true

Метод toString и jasmineToString обеспечивают корректное отображение сообщения об ошибке при провале теста.


Применение в реальных тестах

Ассиметричные матчеры особенно полезны в следующих сценариях:

  1. Тестирование API-ответов, где часть полей динамическая, например ID или timestamp.
  2. Проверка сложных объектов с множеством необязательных свойств.
  3. Проверка коллекций на наличие подмножества элементов, без необходимости полного совпадения.
  4. Валидация типов данных для динамических значений, получаемых из внешних источников.

Важные особенности

  • Ассиметричные матчеры можно использовать внутри других матчеров (objectContaining, arrayContaining), создавая вложенные проверки.
  • При провале теста Jasmine выводит подробное сообщение, показывающее, какое условие не было выполнено.
  • Пользовательские матчеры позволяют инкапсулировать сложную логику сравнения и повторно использовать её в разных тестах.
  • Они работают только с методами toEqual и expect(...).toEqual(...), но не с toBe, так как toBe проверяет строгое совпадение по ссылке.

Практические советы

  • Для проверки типа лучше использовать jasmine.any, а не вручную сравнивать typeof, так как это делает тесты более читаемыми.
  • Для массивов и объектов рекомендуется использовать arrayContaining и objectContaining, чтобы избежать тестов, зависящих от полного совпадения структуры.
  • При создании пользовательских матчеров стоит реализовать toString для информативного вывода ошибок.
  • Комбинация нескольких ассиметричных матчеров позволяет писать самодокументируемые тесты, где сразу видно, какие свойства проверяются, а какие игнорируются.

Ассиметричные матчеры в Jasmine являются фундаментальным инструментом для гибкого и мощного тестирования, позволяя сосредоточиться на логике проверки без жёсткой зависимости от полного совпадения данных. Они делают тесты более устойчивыми к изменениям структуры данных и повышают читаемость кода тестов.