Разделы портала

Онлайн-тренинги

.
Чтение и тестирование JSON-объекта в Cypress
17.06.2021 00:00

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

Начать учить Cypress можно, мало зная о JavaScript – по крайней мере, по моему опыту. Первый затык в обучении у меня произошел, когда я пытался разобраться, как обратиться к данным в JSON-ответе. Эта статья – для всех, кто находится в той же точке. Надеюсь, она поможет вам разобраться.

Обращение к данным в объекте

Начнем с простого, прежде чем нырять в JSON с головой. Допустим, у нас очень простой JSON-объект, содержащий несколько ключей:

fixtures/cars.json
{
"color": "red",
"id": 4,
"available": false
}

Для тестирования такого объекта можно написать вот какой тест. Конечно, в реальном мире мы, возможно, не будем тестировать фикстуру, но для демонстрационных целей мне хочется простоты. Вместо fixture () вы можете представить команду wait (), которая перехватывает сетевой вызов.

cy
.fixture('cars')
.then( car => {
expect(car.color).to.eq("red")
expect(car.id).to.eq(4)
expect(car.available).to.eq(false)
})

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

cy
.fixture('cars')
.then( car => {
expect(car['color']).to.eq("red")
expect(car['id']).to.eq(4)
expect(car['available']).to.eq(false)
})

Зачем выбирать скобочную, а не точечную нотацию, ведь точечная выглядит куда лучше – особенно если нам нужно добраться до атрибута, сидящего довольно глубоко? При скобочной нотации вы можете передавать переменные, то есть написать вот так:

cy
.fixture('cars')
.then(car => {
 
const attr = 'color'
expect(car[attr]).to.eq("red")
 
})

Раз уж мы об этом заговорили, посмотрите мою статью про деструктуризацию – там есть советы по доступу к свойствам внутри JSON-объекта.

Обращение к элементам массива

Добавим еще один элемент в нашу фикстуру car.json. Это будет список характеристик, отформатированный как массив:

{
"color": "red",
"id": 4,
"available": false,
"features": ["speed limiter", "panoramic windshield", "automatic transmission"]
}

Допустим, нам нужен тест на несколько характеристик. Выглядеть он будет как-то так:

cy
.fixture('cars')
.then( car => {
expect(car.features[0]).to.eq('speed limiter')
expect(car.features[1]).to.eq('panoramic windshield')
expect(car.features[2]).to.eq('automatic transmission')
})

Заметьте, мы используем стиль, похожий на вышеупомянутую скобочную нотацию. Вот так можно обратиться к элементам в массиве. Точечной нотации для массивов не существует, поэтому если написать features.1 – вы получите ошибку.

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

cy
.fixture('cars')
.then(car => {
expect(car.features).to.include('speed limiter')
})

Забавный факт – строки в JavaScript могут вести себя как массивы. Это значит, что мы можем обратиться к каждой букве, используя скобочную нотацию, и сделать это с использованием утверждения, как в предыдущем примере.

cy
.fixture('cars')
.then(car => {
expect(car.color[0]).to.eq('r') // access first letter in "red"
expect(car.color).to.include('ed')
})

С массивами можно использовать множество разных утверждений – прочитайте документацию, чтобы узнать об этом больше.

Массив объектов

Скомбинируем теперь объект и массивы. Теперь в нашей фикстуре будет несколько объектов – это очень распространенная ситуация, если вы работаете с API, возвращающим список предметов. Наш JSON-файл будет выглядеть так:

fixtures/cars.json
[
{
"color": "red",
"id": 4,
"available": false,
"features": ["speed limiter", "panoramic windshield", "automatic transmission"]
},
{
"color": "blue",
"id": 7,
"available": true,
"features": ["speed limiter", "automatic transmission"]
}
]

Чтобы протестировать, к примеру, цвет второго объекта, мы совместим оба вышеописанных подхода – обращение к элементу массива и ссылку на ключ объекта:

cy
.fixture('cars')
.then(cars => {
expect(cars[1].color).to.eq('blue')
})

Используя cars[1], мы выбираем второй элемент из массива объектов, а внутри этого объекта используем .color, чтобы выбрать правильное значение из объекта. Иными словами, мы путешествуем по структуре JSON. Конечно, можно использовать и скобочную нотацию и записать этот тест иначе:

cy
.fixture('cars')
.then(cars => {
expect(car[1]['color']).to.eq('blue')
})

Поначалу эти комбинации кажутся навороченными, но со временем вы ощутите их естественность. Тут вам могут помочь инструменты разработчика Chrome. Правый клик на JSON-объекте вызовет меню, из которого можно скопировать путь к конкретному значению. Это работает во многих инструментах разработчика Chrome.


Распространенная ошибка №1: сравнение массивов

Пытаясь путешествовать по JSON-объектам, вы можете столкнуться с разными ошибками. К примеру, это странное поведение при сравнении массива car.features с другим массивом. Допустим, у нас вот такое утверждение:

cy
.fixture('cars')
.then(cars => {
expect(cars[0].features).to.eq(["speed limiter", "panoramic windshield", "automatic transmission"])
})

Оно выглядит так, что должно наверняка сработать, но тест упадет. Что еще страннее, так это ошибка в консоли:


Все выглядит идентичным, почему же падает тест?

Тест падает из-за процесса сравнения в JavaScript. Причина падения похожа на ситуацию, когда '1' == 1 возвращает истину, но '1' === 1 возвращает ложь. Разница в строгости сравнения. При сравнении двух массивов надо использовать deep.eq вместо eq.

Распространенная ошибка №2: невозможно прочитать свойство "x" у undefined

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

Чтобы разобраться с ней, расширим наш файл дополнительными атрибутами:

fixtures/cars.json
[
{
"color": "red",
"id": 4,
"available": false,
"features": ["speed limiter", "panoramic windshield", "automatic transmission"],
"engine": {
"horsepower": 134,
"rpm": 6000,
"fueling system": "turbo/GDI"
}
},
{
"color": "blue",
"id": 7,
"available": true,
"features": ["speed limiter", "automatic transmission"],
"engine": {
"horsepower": 175,
"rpm": 6000,
"fueling system": "MPI"
}
}
]

Мы добавили атрибуты для двигателя. Проверим их:

cy
.fixture('car')
.then(cars => {
expect(cars[0].engines.horsepower).to.eq(134)
})

Тест упадет с ошибкой:


Причина падения в том, что не найден ключ horsepower. Часть "of undefined" говорит о причине конкретнее. Судя по всему, ключ, который должен находиться внутри атрибута engines, не определен.

Обычно в таком случае я вывожу в console.log() не полный путь, а родительский атрибут, и перехожу выше, пока не найду что-то, что мне поможет.

Если так поступить в этом случае, я быстро обнаружу, что опечатался и должен был написать engine вместо engines. Это кажется очевидной ошибкой, но при больших, многоуровневых, переполненных информацией объектах разобраться будет непросто.

Обсудить в форуме