
Привет. Сегодня посмотрим, как тестировать Vue-компоненты. На самом деле, немного поработав с NodeJs и Vue, а так же посмотрев на то, как ребята работают с React — могу сказать, что тесты там пишутся на одних и тех же фреймворках/библиотеках, так что эта информация будет полезна не только Vue-разработчикам.
Первым делом, как обычно, смотрим мануал — https://ru.vuejs.org/v2/guide/unit-testing.html. Если мы разворачиваем проект через Vue CLI, то инструменты тестирования у нас уже есть. Нам говорят, что тестировать мы можем посредством двух инструментов — jest и mocha ̶ ̶и̶ ̶н̶и̶ч̶е̶г̶о̶ ̶с̶м̶е̶ш̶н̶о̶г̶о̶!̶ В этой статье мы рассмотрим, как тестировать vue-компоненты с помощью jest. Туториал на vue.org весьма ужат, поэтому я предлагаю разобрать вот этот ман, а потом уже приступить к тестированию нашего компонента. Открывай мануал, параллельно эту статью и поехали.
Для начала клонируем проект, который предлагает нам автор для тестов:
git clone git@github.com:lex111/jest-vue-example.git
Теперь устанавливаем зависимости:
cd jest-vue-example && npm install
В статье по написанию vue-autoselect-компонента мы работали с yarn
и, если ты хочешь продолжать работать с ним, то в дальнейшем заменяй npm install
на yarn install
, а в случае установки пакетов npm install
на yarn add
, npm install --save-dev
на yarn add --dev
.
Запускаем dev-сервер:
npm run dev
Или
yarn run serve
На выходе видим следующий интерфейс:

Далее читаем всё до заголовка «JEST». Если что-то непонятно или с чем-то есть вопросы — пиши в комменты, обсудим.
Устанавливаем jest:
npm install --save-dev jest
Автор говорит, что нам нужно скомпилировать наши .vue файлы в js файлы перед тем, как запускать тесты. Сам jest делать это не умеет, и чтобы его научить нужно добавить в package.json
следующие строки:
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
}
}
Что, к слову, в склонированном проекте уже сделано. Читаем дальнейший абзац и ставим пакеты:
npm install --save-dev babel-jest vue-jest
Автор говорит, что в директории src/components
требуется создать каталог __test__
, после чего в него добавить файл MessageToogle.spec.js
. Если посмотреть на склонированный проект — все тесты там уже описаны, поэтому для чистоты эксперимента я предлагаю дропнуть из __test__
все файлы и создать чистый файл MessageToogle.spec.js
.
А вот дальше, я думаю, стоит дать пояснения некоторые пояснения касательно кода и тестовых файлов.
Возникает первый вопрос — что за расширение — .spec.js
? Это расширение для тестовых файлов, причём имя файла должно называться так же, как и сам модуль — в нашем случае MessageToogle.
import Vue from 'vue'
import MessageToggle from '@/components/MessageToggle.vue'
describe('MessageToggle.vue', () => {
it('displays default message', () => {
const Ctor = Vue.extend(MessageToggle)
const vm = new Ctor().$mount()
expect(vm.$el.textContent).toContain('default message')
})
})
describe
— как говорит нам документация, создаёт блок для группировки нескольких тестов. Стоит заметить, что этот блок — не обязательный, но он позволяет группировать и логически разбивать тесты на группы, указывая к ним общий заголовок, который передаётся первым параметром. Помимо этого, describe
можно определять внутри другого describe
, что временами весьма удобно, если модуль имеет много тестов.
describe
имеет большой набор API, с которыми мы познакомимся в процессе.
Каждый тест в блоке describe
начинается к ключевого слова it
. Если посмотреть в документацию, то про it
мы там мало что найдём, потому что it
— это алиас для ключевого слова test
, о котором, в свою очередь, в документации много чего сказано. Важно понимать, что it
-методы — это и есть тесты. В документации есть прекрасный пример, который я нагло продублирую — допустим, нам нужен тест, который проверяет, что метод вернёт 0:
test('did not rain', () => {
expect(inchesOfRain()).toBe(0);
});
Первый аргумент, как я уже говорил, — заголовок теста, он будет выведен в консоли. Второй — callback-метод, который выполняет сам тест. В этом методе мы видим ещё один важный элемент — метод expect
— аналог утверждений, о которых мы говорили в предыдущих статьях. expect
можно расценивать как ожидание нами какого-то результата, собственно, документация и переводит этот метод как «ожидание». В примере выше мы ожидаем, что функция inchesOfRain
вернёт 0. Как ты уже мог догадаться, ожидай большое количество и ознакомится с ними можно по этой ссылке.
А теперь вернёмся к мануалу. Мы остановились на этом примере:
import Vue from 'vue'
import MessageToggle from '@/components/MessageToggle.vue'
describe('MessageToggle.vue', () => {
it('displays default message', () => {
const Ctor = Vue.extend(MessageToggle)
const vm = new Ctor().$mount()
expect(vm.$el.textContent).toContain('default message')
})
})
Теперь можно его разобрать:
Описывается группа текстов с заголовком MessageToggle.vue
.
В группе всего один тест, определяемый в callback-функции метода it
с названием displays default message
. Дальше идут 2 строчки, с методами которых мы познакомимся чуть позже, а пока просто кратко попытаюсь их объяснить:
Vue.extend
создаёт подкласс, принимающий параметры компонента в аргументе, а если проще — создаёт класс, позволяющий в дальнейшем создать наш компонент через обычный вызов конструктора.
Второй строкой мы создаём компонент и монтируем его с помощью метода $mount
. $mount
— это хук жизненного цикла, который «встраивает» в DOM-дерево наш компонент. Звучит сложно и непонятно, но в следующих материалах мы подробно с этим поработаем 🙂
И, наконец, автор создаёт ожидание через expect.
В качестве аргумента, как обычно, идёт то, что мы хотим проверить. $el
— это свойство уже вмонтированного в DOM
-дерево компонента, позволяющее дёргать DOM
-элементы. Когда мы обратились к какому-либо DOM
-элементу, будь то <p>
, <button>
или что-то другое, мы можем использовать стандартное js-api
. В примере автор использует свойство textContent, которое возвращает (или позволяет задать) текстовое содержимое элемента или его потомков. Тем самым в качестве аргумента мы передаём текст, описанный внутри компонента MessageToggle
. Откроем компонент MessageToggle.vue
:
<template>
<div>
<p>{{msg}}</p>
<button id="toggle-message" @click="toggleMessage">
Change message
</button>
</div>
</template>
<script>
export default {
name: 'message-toggle',
data: () => ({
msg: 'default message'
}),
methods: {
toggleMessage () {
this.msg = this.msg === 'message' ? 'toggled message' : 'message'
}
}
}
</script>
Видно, что в тег <p>
подставляется строка со значением default message
. Не трудно догадаться, что метод toContain
, в рассматриваемом нами тесте, позволяет проверить, содержится ли переданная в него строка в аргументе, полученном внутри expect
. Чтобы убедится, что тест корректно отрабатывает, автор предлагает добавить алиас для команды jest
в файл package.json
:
"unit": "jest"
Что, кстати, в склонированном проекте уже сделано:

И запустить эту команду:

Едем дальше — автор предлагает установить vue-test-utils
— библиотеку, упрощающую процесс написания тестов, что мы и сделаем:
npm install --save-dev vue-test-utils
И заменяем ранее написанный нами тест на следующий:
import { shallowMount } from '@vue/test-utils'
import MessageToggle from '@/components/MessageToggle.vue'
describe('MessageToggle.vue', () => {
it('displays default message', () => {
const wrapper = shallowMount(MessageToggle)
expect(wrapper.text()).toContain('default message')
})
})
Смотрим, что у нас тут из нового:
shallowMount
— как говорит документация, аналогично $mount, метод создаёт объект, который содержит примонтированный (читай — встроенный) и отрендеренный vue-компонент, но вместо дочерних компонентов у него заглушки. И это, я считая, круто — нам не нужно рендерить всю вложенную логику, ибо временам и это бывает очень и очень затратно, а проверить нам нужно один конкретный компонент..text()
— вспомогательный метод объекта, который мы вернули выше. Метод возвращает текст, вложенный в компонент. Согласись, это явно удобней и понятней, чем использоватьvm.$el.textContent
. Методов вроде text() — много, подробней можно посмотреть здесь.
Запускаем:

Результат не изменился, чего и следовало ожидать. Но вот на код мы потратили гораздо меньше времени.
Теперь, чтобы не терять время, мы сразу разберём следующий тест:
import { mount } from '@vue/test-utils'
import MessageToggle from '@/components/MessageToggle.vue'
describe('MessageToggle.vue', () => {
it('displays default message', () => {
const wrapper = mount(MessageToggle)
expect(wrapper.text()).toContain('default message')
})
it('toggles message when button is clicked', () => {
const wrapper = mount(MessageToggle)
const button = wrapper.find('#toggle-message')
const p = wrapper.find('p')
button.trigger('click')
expect(p.text()).toBe('message')
button.trigger('click')
expect(p.text()).toBe('toggled message')
})
})
Первый it нам знаком, поэтому разбираем второй.
После монтирования компонента мы ищем, с помощью метода find
из библиотеки vue-test-utils селектор с id
toggle-message
и селектор по тегу <p>
. Затем мы провоцируем событие click
, с помощью метода trigger
и проверяем, какой текст содержит тег <p>.
Затем вызываем событие второй раз и проверяем текст ещё раз. Если зайти в интерфейс и понажимать на кнопку руками — будет понятно, что текст выводится именно такой
В мануале есть ещё одни раздел — тестирование через снимки, но чтобы не растягивать статью я думаю, что мы разберём это в одной из следующий статей.
Я думал добавить в рамках этой же статьи тестирование нашего компонента, но статья в этом случае получается слишком большой, поэтому протестируем в следующей.
Спасибо за внимание 🙂
Twitter — https://twitter.com/SeniorJun — анонсы, мысли и обсуждения. Тут обычно то, что в канал я не рискну писать 🙂
Поддержать развитие канала — https://www.patreon.com/junsenior