СТАТЬЯ
Тестирование событий в Vue.js, часть 2
Из Тестирование приложений Vue.js Эдда Йербурга
__________________________________________________________________
Скидка 37% на Тестирование приложений Vue.js. Просто введите код fccyerburgh в поле для кода скидки при оформлении заказа на manning.com.
_______________________________________________________________________
Часть 2 охватывает:
- Тестирование форм ввода
- Ограничения jsdom
Продолжая тему тестирования событий в Vue.js, мы продолжим то, о чем говорили в Части 1, и поговорим о тестировании форм ввода и ограничениях jsdom.
Тестирование форм ввода
От контактных форм до форм регистрации и форм входа — формы ввода есть везде! Формы ввода могут содержать много логики для обработки проверки и выполнения действий с входными значениями, и эту логику необходимо протестировать.
В этом разделе вы узнаете, как тестировать формы, написав тесты для компонента формы. В форме есть поле для ввода электронной почты, чтобы пользователи могли ввести свой адрес электронной почты, два переключателя, с помощью которых пользователи могут выбрать, хотят ли они участвовать в конкурсе, и кнопка подписки (рис. 1).
Когда пользователь отправляет форму, компонент должен отправить запрос POST в API с адресом электронной почты, введенным пользователем, и значением переключателей. Для простоты он не будет включать никакой логики проверки.
Спецификации формы таковы:
- Он должен размещать значение ввода электронной почты при отправке
- Он должен размещать значения радиокнопок вступить в соревнование при отправке.
Первый тест на запись проверяет, что вы отправляете запрос POST с адресом электронной почты, введенным пользователем. Для этого вам нужно научиться тестировать значения ввода текста.
Тестирование ввода текста
Элементы ввода используются для сбора данных, введенных пользователем. Часто приложения используют эти данные для выполнения действия, например отправки данных во внешний API.
Интересно отметить, что элементы ввода имеют собственное состояние. Различные типы элементов хранят свое состояние в разных свойствах. Входные текстовые элементы управления, такие как text
, email
и address
, сохраняют свое состояние в свойстве value
.
Чтобы проверить, правильно ли элементы ввода используют value
, вы должны иметь возможность управлять свойством value
ввода в своих тестах. Многие люди не понимают, как установить значение формы ввода. Распространенным заблуждением является то, что имитация события keydown
со свойством key
изменяет значение элемента. Это неправильно. Чтобы изменить свойство value
ввода в JavaScript, вам нужно установить свойство value
непосредственно в элементе:
document.querySelector('input[type="text"]').value = 'some value'
Когда вы пишете тест, который использует ввод value
, вы должны установить value
вручную, прежде чем активировать ввод теста:
wrapper.find('input[type="text"]').value = 'Edd' wrapper.find('input[type="text"]').trigger('change') expect(wrapper.text()).toContain('Edd')
В Vue принято использовать директиву v-model
для создания двусторонней привязки между входными данными value
и данными экземпляра компонента. Для связанного значения любые изменения, которые пользователь вносит в значение формы, обновляют значение данных экземпляра компонента, а любые изменения значения свойства экземпляра применяются к входному свойству value
(листинг 8).
ИНФОРМАЦИЯ Если вы не знакомы с директивой v-model
, вы можете прочитать о ней в документации Vue — https://vuejs.org/v2/api/#v-model.
Листинг 8. Использование v-model для привязки данных
new Vue({ el: '#app', data: { message: 'initial message' // #A }, template: '<input type="text" v-model="message" />', // #B mounted() { setTimeout(() => this.message = '2 seconds', 2000) // #C } })
#A Начальное значение сообщения
#B Привязать элемент ввода к данным сообщения. Начальное значение элемента ввода — начальное сообщение
#C Обновляет значение элемента ввода до двух секунд.
К сожалению, установка входного свойства value
напрямую не обновит связанное значение. Чтобы обновить v-model
текстового ввода, вам нужно установить value
для элемента, а затем вызвать событие change
для элемента, чтобы принудительно обновить привязанное значение. Это связано с реализацией v-модели в ядре Vue и может измениться в будущем. Вместо того, чтобы полагаться на внутреннюю реализацию v-модели, вы можете использовать метод оболочки setValue
. Метод setValue
устанавливает значение на входе и обновляет связанные данные, чтобы использовать новое значение (листинг 9).
Листинг 9. Обновление значения и значения v-модели ввода в тесте
const wrapper = shallowMount(Form) const input = wrapper.find('input[type="email"]') // #A input.setValue('[email protected]') // #B
#A Получить оболочку элемента ввода
#B Установить значение элемента ввода и обновить связанные данные
В тесте, который вы пишете, вам нужно установить значение элемента ввода с помощью setValue
, инициировать отправку формы и проверить, что данные отправляются в запросе POST. Чтобы утверждать, что запрос POST отправлен, вам нужно решить, как вы будете делать запрос POST.
Обычный способ выполнения HTTP-запросов — использование библиотеки. Вы будете использовать популярную библиотеку axios. Вы можете отправить запрос POST, используя метод axios post
, который принимает URL-адрес и необязательный объект данных в качестве аргументов:
axios.post('https://google.com', { data: 'some data' })
ИНФОРМАЦИЯ axios – это библиотека для создания HTTP-запросов, аналогичная собственному методу fetch
. Нет особой причины использовать эту библиотеку вместо другой библиотеки HTTP; вы используете его как пример.
Приложение, над которым вы работаете, уже настроено на использование axios. Он использует библиотеку vue-axios для добавления свойства экземпляра axios
Vue (это можно увидеть в src/main.js). Это означает, что вы можете вызывать axios из компонента:
this.axios.post('https://google.com', { data: 'some data' })
Чтобы убедиться, что тест проверяет, что вы вызвали метод axios post
, создайте фиктивный объект axios в качестве свойства экземпляра и убедитесь, что он был вызван с правильными аргументами.
Вы можете утверждать, что фиктивная функция Jest была вызвана с правильными аргументами, используя toHaveBeenCalledWith
matcher.
toHaveBeenCalledWith
matcher утверждает, что фиктивная функция была вызвана с переданными аргументами. В этом тесте вы проверяете, что axios post
был вызван с правильным URL-адресом и объектом, содержащим свойство электронной почты:
expect(axios.post).toHaveBeenCalledWith(url, { email: '[email protected]' })
Проблема в том, что когда вы добавляете дополнительные свойства к данным axios в следующем тесте, тест завершается неудачно, потому что объекты-аргументы не равны друг другу. Вы можете проверить этот тест на будущее, используя функцию Jest expect.objectContaining
. Этот помощник используется для сопоставления некоторых свойств в объекте данных, а не для проверки точного соответствия объекта (листинг 10).
Листинг 10. Использование objectContaining
const data = expect.objectContaining({ email: '[email protected]' }) expect(axios.post).toHaveBeenCalledWith(url, data)
Теперь тест всегда проходит, если свойство электронной почты отправляется с правильным значением.
Пришло время добавить тест. Он выглядит довольно большим, но если вы разберете его, перед запуском события отправки потребуется много настроек. Скопируйте код из листинга 11 в src/components/__tests__/Form.spec.js.
Листинг 11. Тестирование макета было вызвано с привязанным к v-модели значением входной формы
test('sends post request with email on submit', () => { const axios = { // #A post: jest.fn() } const wrapper = shallowMount(Form, { // #B mocks: { axios } }) const input = wrapper.find('input[type="email"]') // #C input.setValue('[email protected]') // #D wrapper.find('button').trigger('submit') // #F const url = 'http://demo7437963.mockable.io/validate' const expectedData = expect.objectContaining({ email: '[email protected]' }) expect(axios.post).toHaveBeenCalledWith(url, expectedData) // #G })
#A Создать фиктивный объект axios со свойством post
#B Поверхностное монтирование формы с фиктивным axios
#CПолучить оболочку элемента ввода электронной почты
#D Установить значение ввода
#FОтправить форму
>#G Утверждает, что axios.post был вызван с правильным значением URL в качестве первого аргумента.
Прежде чем двигаться дальше и запускать тесты, вам нужно обновить предыдущий тест. В настоящее время предыдущий тест выдаст ошибку, потому что компонент формы пытается вызвать axios.post
, что равно undefined
. На практике это проблема дырявого ведра. Зависимости свойства экземпляра не существует, и вам нужно исправить дыры (смоделировать свойство экземпляра), чтобы избежать ошибок в тесте.
В src/components/__tests__/Form.spec.js замените код для создания оболочки в тесте отправляет форму при отправке формы следующими фрагментами кода:
const wrapper = shallowMount(Form, { mocks: { axios: { post: jest.fn() } } })
Теперь обновите компонент кодом из листинга 12.
Листинг 12. Компонент формы
<template> <form name="email-form" @submit="onSubmit"> <input type="email" v-model="email" /> // #A <button type="submit">Submit</button> </form> </template> <script> export default { data: () => ({ email: null }), methods: { onSubmit (event) { this.axios.post('http://demo7437963.mockable.io/validate', { // #B email: this.email }) this.$emit('form-submitted') } } } </script>
#A Привяжите ввод к свойству электронной почты компонента с помощью директивы v-model
#B Вызов axios.post
Вы видели, как написать тест для компонентов, которые используют значение элемента ввода в утверждении. Вы можете использовать setValue
для всех элементов ввода, которые используют текстовый элемент управления, например text
, textarea
и email
. Вам нужно использовать другой метод для других типов ввода, таких как переключатели.
Тестирование переключателей
Радиокнопки — это кнопки, которые вы можете выбрать. Вы можете выбрать только одну кнопку из группы переключателей за раз. Тестирование переключателей немного отличается от тестирования элемента ввода текста.
На нашем сайте проходит конкурс! Каждый, кто видит форму регистрации, имеет шанс принять участие в конкурсе. Когда форма будет отправлена, вы отправите выбор пользователей (используя значение переключателя) в запросе POST к API. Я чувствую еще один тест, чтобы написать!
Тестирование радиокнопок аналогично тестированию форм ввода. Вместо внутреннего состояния value
внутреннее состояние переключателей — checked
. Чтобы изменить выбранный переключатель, вам нужно установить свойство checked
для ввода переключателя напрямую (листинг 13).
ИНФОРМАЦИЯ Свойство checked
аналогично свойству value
. Это состояние переключателя, которое изменяется пользователем, взаимодействующим с переключателем.
Листинг 13. Обновление значения и значения v-модели переключателя, введенного в тесте
const wrapper = shallowMount(Form) const radioInput = wrapper.find('input[type="radio"]') // #A radioInput.element.checked = true // #B
#A Получить оболочку элемента радио-ввода
#B Установить проверенное свойство элемента радио-ввода напрямую
При прямой установке проверенного значения возникает та же проблема, что и при прямой установке значения текстового элемента управления. v-model
не обновляется. Вместо этого вы должны использовать метод setChecked
:
wrapper.find('input[type="radio"]').setChecked()
Необходимо написать два теста. Первый тест проверяет, что форма по умолчанию отправляет enterCompetition
как истину, потому что флажок «да» установлен по умолчанию. Для краткости я не буду показывать вам, как написать этот тест. Тест, который вы напишете, проверяет переключатель «нет», отправляет форму и утверждает, что enterCompetition
ложно.
Это большой старый тест, но опять же, это в основном настройка. Добавьте код из листинга 14 в блок describe
в src/components/__tests__/Form.spec.js.
Листинг 14. Тестирование вызова компонента с правильными значениями
test('sends post request with enterCompetition checkbox value on submit', () => { const axios = { post: jest.fn() } const wrapper = shallowMount(Form, { // #A mocks: { axios } }) const url = 'http://demo7437963.mockable.io/validate' wrapper.find('input[value="no"]').setChecked() // #B wrapper.find('button').trigger('submit') // #C expect(axios.post).toHaveBeenCalledWith(url, expect.objectContaining({ // #D enterCompetition: false })) })
#A Компонент формы для мелкого монтирования с фиктивным объектом axios
#B Установите флажок "Нет" в качестве флажка
#C Отправить форму
#D Подтвердить, что axios.post был вызван с правильным значением enterCompetition
Чтобы пройти тесты, вам нужно добавить радиовходы и обновить метод onSubmit
, чтобы добавить значение enterCompetition
к объекту данных, отправленному с помощью axios.post
. Добавьте следующие переключатели в блок ‹template› в src/components/Form.vue:
<input v-model="enterCompetition" value="yes" type="radio" name="enterCompetition" /> <input v-model="enterCompetition" value="no" type="radio" name="enterCompetition" /> Add enterCompetition to the default object: data: () => ({ email: null, enterCompetition: 'yes' }),
Наконец, обновите вызов axios, чтобы отправить свойство enterCompetition
. Тест ожидает логическое значение, но значения переключателей являются строками, и вы можете использовать оператор строгого равенства, чтобы установить enterCompetition как логическое значение:
this.axios.post('http://demo7437963.mockable.io/validate', { email: this.email, enterCompetition: this.enterCompetition === 'yes' })
Запустите модульные тесты и посмотрите, как они проходят: npm run test:unit
. Вы добавили все возможные тесты для проверки функциональности формы. Я хотел бы добавить еще один тест, чтобы проверить, что отправка формы не вызывает перезагрузку, но использование jsdom делает невозможным запись. Как любой родитель боится птиц и разговоров с пчелами, я всегда опасаюсь ограничений разговоров о жосдоме.
Понимание ограничений jsdom
Чтобы запустить модульные тесты Vue в Node, вам нужно использовать jsdom для имитации среды DOM. В большинстве случаев это прекрасно работает, но иногда вы сталкиваетесь с проблемами из-за нереализованных функций.
В jsdom есть две нереализованные части веб-платформы:
Макет — это вычисление позиций элементов, в результате чего методы DOM, такие как Element.getBoundingClientRects
, ведут себя неожиданным образом. В этой статье вы не встретите никаких проблем с этим, но вы можете столкнуться с этим, если используете положение элементов для расчета стиля в своих компонентах.
Другая нереализованная часть — это навигация. В jsdom нет концепции страниц, и вы не можете делать запросы и переходить на другие страницы. Это означает, что события отправки ведут себя не так, как в браузере. В браузере по умолчанию событие отправки делает запрос GET, который вызывает перезагрузку страницы. Это почти никогда не является желательным поведением, и вам нужно написать код, чтобы событие не делало запрос GET для перезагрузки страницы.
В идеале вы должны написать модульный тест, чтобы убедиться, что вы предотвращаете перезагрузку страницы. С jsdom вы не можете сделать это без экстремальных насмешек, которые не стоят потраченного времени.
Вместо написания модульного теста вам нужно написать сквозной тест, чтобы убедиться, что отправка формы не перезагружает страницу. Я не рассказываю о написании сквозных тестов, но пока вы добавите код без теста.
Чтобы остановить перезагрузку страницы, вы можете добавить модификатор события в директиву v-bind
. Откройте src/components/Form.vue и добавьте модификатор события .prevent
к отправке v-bind
:
<form name="email-form" @submit.prevent="onSubmit">
Модификатор вызывает event.preventDefault
, который останавливает перезагрузку страницы при отправке.
Две нереализованные части jsdom — это навигация и макет. Важно понимать эти ограничения, и вы можете защититься от них. Когда вы столкнетесь с ограничениями, вместо насмешек вы должны дополнить свои модульные тесты сквозными тестами, которые проверяют функциональность, основанную на нереализованных функциях jsdom.
Теперь, когда вы предотвращаете дефолт, у вас есть полнофункциональная форма. Вы можете открыть сервер разработки и посмотреть: npm run serve
. Теперь очевидно, что эта форма далеко не готова для общественного потребления. Он не имеет стиля и невероятно уродлив. Дело в том, что теперь у вас есть набор юнит-тестов, которые проверяют основную функциональность, и вы можете свободно добавлять стили, не замедляясь из-за юнит-тестов.
ПРИМЕЧАНИЕ Чтобы увидеть, как выглядит готовое приложение, перейдите по ссылке https://github.com/eddyerburgh/vue-email-signup-form-application.
Давайте вспомним, что вы узнали из этой статьи.
Сводка
Из этой статьи вы узнали следующее:
- Вы можете
trigger
нативных событий DOM с помощью метода триггера оболочки - Вы можете проверить, что компонент отвечает на эмитированные события, вызвав
$emit
для экземпляра дочернего компонента. - Вы можете проверить, что компонент сгенерировал пользовательское событие Vue с помощью метода оболочки
emitted
. - jsdom не реализует навигацию или макет
Упражнения
- Как вы имитируете нативное событие DOM в тестах?
Ответ: с помощью метода оболочки trigger
2. Как бы вы проверили, что родительский компонент реагирует на событие, генерируемое дочерним компонентом?
Ответ: создав событие для экземпляра дочернего компонента с помощью метода $emit
Если вы хотите узнать больше о книге, загляните в нашу читалку liveBook здесь и посмотрите эту презентацию.
Об авторе:
Эдд Йербург — опытный разработчик JavaScript и основной участник Vue. Он является основным автором официальной тестовой библиотеки Vue и видной фигурой в сообществе тестирования Vue.
Первоначально опубликовано на freecontent.manning.com.