Тестирование массивов и объектов с помощью Chai.js

Когда дело доходит до тестирования массивов и объектов с помощью Chai.js, иногда выбор помеченных свойств и утверждений сбивает с толку. вложенный? глубокий? собственный? включить? все? В этой статье мы расскажем, как стать отличным помощником при тестировании массивов и объектов с помощью Chai.js.

Если вы пропустили часть 1, различия между свойством пометки и цепным методом, перед продолжением стоит просмотреть эту статью, чтобы наверстать упущенное.

Равенство

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

expect([1, 2, 3]).to.equal([1, 2, 3]); // fails

Это может быть удивительно, а иногда и просто разочаровывать, особенно для программистов, которые переходят на Javascript с других языков. Равенство, выраженное в приведенном выше примере, на самом деле является основной механикой Javascript, а не Chai.js. Откройте сеанс узла и попробуйте следующее:

$ node
> [1,2,3] === [1,2,3]
false

Равенство Javascript является строгим; оно проверяет, относится ли выражение слева к той же точке памяти, в которой находится выражение справа. В приведенном выше примере есть две копии [1,2,3] (одна слева и одна справа). Поскольку каждая копия имеет собственный адрес в памяти, строгое равенство Javascript считает их неравными, поскольку они не имеют одинаковых идентификаторов.

В Chai.js утверждение equal, как и большинство других включенных утверждений Chai, использует строгое равенство Javascipt.

Во время тестирования часто бывает, что строгое равенство нежелательно, а вместо этого требуется равенство в том смысле, что два объекта «эквивалентны» в том смысле, что они имеют одинаковое содержание.

Глубокое равенство

Chai.js решает эту проблему, предоставляя второе утверждение равенства, eql. Eql основан на проекте deep-eql. Он работает, глядя на содержимое сравниваемых выражений.

expect([1, 2, 3]).to.eql([1, 2, 3]); // passes

Сходство кажется простым на первый взгляд, но становится более сложным, когда у этих выражений есть дети, которые сами имеют детей. Глубокое равенство сравнивает одинаковость со всеми глубинами.

expect([{a:1}, {b:2}]).to.eql([{a:1}, {b:4}]); // fails
expect([{a:1}, {b:2}]).to.eql([{a:1}, {b:2}]); // passes

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

expect({ a: 1 }).to.equal({ a: 1 }); // fails
expect({ a: 1 }).to.eql({ a: 1 }); // passes

Неупорядоченное глубокое равенство

Глубокое равенство - отличный подход, но при сравнении массивов он обеспечивает соблюдение как порядка, так и содержимого. Часто бывает так, что при тестировании порядок не имеет значения, а содержание имеет значение.

expect([1,2,3]).to.eql([3,2,1]); // fails

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

expect([1,2,3]).to.have.members([3,2,1]); // passes
expect([1,2,3]).to.have.members([1,2,3]); // passes 

Зона опасности. Распространенной ошибкой является запись вышеупомянутого утверждения с помощью include вместо have.

expect([1,2,3]).to.include.members([3,2,1]); // passes

Различие между include и have очень важно. Have - это косметическое свойство. Это не оправдывает ожиданий, но облегчает чтение. Однако include - это цепной метод. Он устанавливает флаг, который изменяет поведение членов, чтобы проверять только те значения, которые находятся в тестируемом значении, независимо от того, какие другие значения находятся в массиве.

expect([1,2,3,4]).to.include.members([3,2,1]); // passes
expect([1,2,3,4]).to.have.members([3,2,1]); // fails

Между порядком и точностью членов существует таблица истинности 2x2, в которой сопоставитель должен использоваться в каких случаях.

  • Вопросы целостности заказа - .to.have.ordered.members
  • Неупорядоченная целостность имеет значение - .to.have.members
  • Вопросы неупорядоченного членства - .to.include.members
  • Упорядоченные вопросы о членстве - невозможный случай

Еще одна путаница при тестировании членства в неупорядоченном массиве - это два флаговых свойства Chai any и all. Начиная с версии 4.x, эти флаги не изменяют поведение members assertion (они влияют только на утверждение keys, которое обсуждается ниже).

И и все могут быть включены, если это облегчает чтение ожидания, но эффективно действуют как косметические свойства в этой точке и рискуют просто запутать других инженеры, знакомые с тем, что any и all делают с другими утверждениями. Следующие два функционально эквивалентны.

expect([1,2,3]).to.have.members([3,2,1]); // passes
expect([1,2,3]).to.have.all.members([3,2,1]); // passes

Точно так же с массивом примитивных значений (не объектов) можно записать такое же ожидание с помощью eql или .ordered.members.

expect([1,2,3]).to.eql([1,2,3]); // passes
expect([1,2,3]).to.have.ordered.members([1,2,3]); // passes

Разница между .ordered.members и .eql, помимо способа чтения для обычных читателей, заключается в сообщении об ошибке.

AssertionError: expected [ 1, 2, 3 ] to have the same ordered members as [ 3, 2, 1 ]

60 саженей глубокого равенства

Разница между выбором eql и .ordered.members становится более очевидной при сравнении массивов объектов. Упомянутое ранее, eql - это утверждение равенства в Chai.js, которое будет выполнять глубокое равенство вместо строгого равенства. Третий способ сравнить два массива примитивных значений - использовать свойство пометки deep.

expect([1, 2, 3]).to.deep.equal([1, 2, 3]); // passes

Хотя приведенное выше ожидание функционально эквивалентно использованию eql, разница, которую делает флаг deep, заключается в том, что он смешивается с другими утверждениями, такими как members.

expect([ {a:1} ]).to.have.deep.members([ {a:1} ]); // passes
expect([ {a:1} ]).to.have.members([ {a:1} ]); // fails

Важная концепция: по умолчанию все утверждения в Chai выполняют строгое сравнение на равенство. Таким образом, утверждение, что массив объектов имеет объект-член, приведет к строгому сравнению этих двух объектов. Добавление флага deep к утверждению, чтобы вместо этого использовать глубокое равенство для сравнения.

Если это было немного шокирующим, на этом этапе можно отреагировать: «Вау! Это глубоко. "

Равенство свойств объекта

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

const obj = { a: 1, b: 2 };
expect(obj.c).to.not.be.undefined;

Основная проблема при написании тестов таким образом - это сообщение об ошибке:

AssertionError: expected undefined not to be undefined

Неважно, как долго кто-то занимается программированием или насколько хорошо он знает кодовую базу, сообщения об ошибках, подобные этому, делают проблемы излишне трудными для решения.

Как и ожидалось, Chai предоставляет утверждения keys и property, которые могут утверждать существование одного свойства (property) или нескольких свойств ( ключи) на объекте.

expect({ a: 1, b: 2 }).to.have.property('b'); // passes
expect({ a: 1, b: 2 }).to.have.keys([‘a’, ‘b’]); // passes

В качестве дополнительного бонуса, если значение имеет значение, утверждение property можно использовать со вторым параметром, ожидаемым значением.

expect({ a: 1, b: 2 }).to.have.property(‘b’, 2); // passes

Равенство свойств глубоких объектов

Альтернативный способ проверить наличие свойства - использовать утверждение include. Include действует так же, как и с массивами, проверяя, что заданные свойства являются методами проверяемого значения, а не то, что данные свойства являются целым тестируемым значением.

expect({ a: 1, b: 2 }).to.include({ b: 2 }); // passes

Как и в других примерах в этой статье, разница заключается в двух аспектах: во-первых, в сообщении об ошибке, которое распечатывается при сбое; и, во-вторых, как он взаимодействует с флагом deep.

expect({ a: { c: 3 } }).to.include({ a: { c: 3 } }); // fails
expect({ a: { c: 3 } }).to.deep.include({ a: { c: 3 } }); // passes

deep.include полезен в некоторых случаях, но для чего-то большего, чем уровень или более глубокий, может стать громоздким. Для действительно глубоких проверок можно использовать свойство пометки вложенного. Вложенный сигнализирует, что во всех местах, где могло бы быть имя свойства (ключ), теперь это путь к свойству. Это особенно полезно при работе с чрезвычайно сложными структурами JSON.

const obj = {
  query: {
    bool: {
      filter: {
        term: { id: '12345' }
      }
    }
  }
};
// passes
expect(obj).to.have.nested.property('query.bool.filter.term.id');
// passes (works with deep as well)
expect(obj).to.have.deep.nested
  .property('query.bool.filter.term', { id: '12345' });

Что-то интересное в вложенном заключается в том, что на массивы также можно ссылаться по пути. Если в приведенном выше примере filter стал массивом терминов, а не просто одним термином, доступ к массиву по индексу можно было бы включить во вложенное имя свойства.

const obj = {
  query: {
    bool: {
      filter: [{
        term: { id: '12345' }
      }]
    }
  }
};
// passes
expect(obj).to.have.nested.property('query.bool.filter[0].term.id');

Свойство вложенного пометки работает как с утверждением property, так и с ключами.

Сначала это может показаться немного подавляющим, все вещи, которые необходимы для тестирования массивов и объектов, но после нескольких раундов практики это станет второй натурой. Обязательно посетите Документацию Chai.js, чтобы узнать о других новинках, которые вас ждут, или прочитайте код самостоятельно, чтобы получить еще глубокое представление о том, как он работает.