Модульное тестирование Vue-компонентов

Простой пример

Модульное тестирование — фундаментальная часть разработки программного обеспечения. В модульных тестах выполняются небольшие фрагменты (единицы) кода в изоляции для упрощения добавления новых функциональных возможностей и отслеживания ошибок. Однофайловые компоненты Vue позволяют просто писать модульные тесты для компонентов в изоляции. Это поможет вам разрабатывать новую функциональность с уверенностью, что вы не ломаете работу существующей, и помогает другим разработчикам понять, как работает компонент.

Этот простой пример проверяет, отрисовывается ли какой-либо текст:

<template>
  <div>
    <input v-model="username">
    <div
      v-if="error"
      class="error"
    >
      {{ error }}
    </div>
  </div>
</template>

<script>
export default {
  name: 'Hello',
  data () {
    return {
      username: ''
    }
  },

  computed: {
    error () {
      return this.username.trim().length < 7
        ? 'Имя пользователя должно быть длиннее 6 символов'
        : ''
    }
  }
}
</script>
import { shallowMount } from '@vue/test-utils'
import Hello from './Hello.vue'

test('Hello', () => {
  // отрисовываем компонент в изоляции
  const wrapper = shallowMount(Hello)

  // устанавливаем `username` меньше 7 символов, без учёта пробелов
  wrapper.setData({ username: ' '.repeat(7) })

  // проверяем, что ошибка отобразилась
  expect(wrapper.find('.error').exists()).toBe(true)

  // обновляем имя, чтобы оно было достаточно длинным
  wrapper.setData({ username: 'Александр' })

  // проверяем, что ошибка исчезла
  expect(wrapper.find('.error').exists()).toBe(false)
})

Вышеприведённый фрагмент кода показывает, как проверить, отрисовывается ли сообщение в зависимости от длины имени пользователя. Он демонстрирует общую идею модульного тестирования Vue-компонентов: отрисовка компонента и проверка, что разметка соответствует состоянию компонента.

Зачем тестировать?

У модульных тестов компонентов есть множество преимуществ:

Автоматическое тестирование позволяет большим командам разработчиков поддерживать сложную кодовую базу.

Начало работы

Vue Test Utils — официальная библиотека для модульного тестирования Vue-компонентов. Шаблон webpack vue-cli идёт в комплекте с Karma или Jest, как с хорошо поддерживаемыми программами запуска тестов, и есть некоторые руководства в документации Vue Test Utils.

Пример из реального мира

Модульные тесты должны быть:

Давайте продолжим работу с предыдущим примером, представив идею фабричной функцию, чтобы сделать наш тест более компактным и читаемым. Компонент должен:

Давайте сначала рассмотрим код компонента:

<template>
  <div>
    <div class="message">
      {{ message }}
    </div>
    Введите имя пользователя: <input v-model="username">
    <div
      v-if="error"
      class="error"
    >
      Пожалуйста, введите имя пользователя длиной не менее семи символов.
    </div>
  </div>
</template>

<script>
export default {
  name: 'Foo',

  data () {
    return {
      message: 'Добро пожаловать в книгу рецептов Vue.js',
      username: ''
    }
  },

  computed: {
    error () {
      return this.username.trim().length < 7
    }
  }
}
</script>

Список из того, что нам нужно проверить:

И наша первая попытка теста:

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
  it('отрисовывает сообщение и правильно реагирует на пользовательский ввод', () => {
    const wrapper = shallowMount(Foo, {
      data: {
        message: 'Привет, мир',
        username: ''
      }
    })

    // посмотреть, отобразилось ли сообщение
    expect(wrapper.find('.message').text()).toEqual('Привет, мир')

    // проверить, что ошибка отрисовалась
    expect(wrapper.find('.error').exists()).toBeTruthy()

    // обновить `username` и проверить, что ошибка больше не отрисовалась
    wrapper.setData({ username: 'Александр' })
    expect(wrapper.find('.error').exists()).toBeFalsy()
  })
})

Есть несколько проблем с вышеприведённым кодом:

В примере ниже тестирование улучшается с помощью следующих правил:

Обновлённый тест:

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const factory = (values = {}) => {
  return shallowMount(Foo, {
    data () {
      return {
        ...values
      }
    }
  })
}

describe('Foo', () => {
  it('отрисовывает приветственное сообщение', () => {
    const wrapper = factory()

    expect(wrapper.find('.message').text()).toEqual('Добро пожаловать в книгу рецептов Vue.js')
  })

  it('отрисовывает ошибку, когда имя пользователя меньше 7 символов', () => {
    const wrapper = factory({ username: '' })

    expect(wrapper.find('.error').exists()).toBeTruthy()
  })

  it('отрисовывает ошибку, когда имя пользователя состоит только из пробелов', () => {
    const wrapper = factory({ username: ' '.repeat(7) })

    expect(wrapper.find('.error').exists()).toBeTruthy()
  })

  it('не отрисовывает ошибку, когда имя пользователя равно 7 символам или более', () => {
    const wrapper = factory({ username: 'Александр' })

    expect(wrapper.find('.error').exists()).toBeFalsy()
  })
})

Следует отметить:

В верхней части мы объявляем фабричную функцию, когда объединяет объект values в свойство data и возвращает новый экземпляр wrapper. Таким образом, нам не нужно дублировать const wrapper = shallowMount(Foo) в каждом тесте. Ещё одним большим преимуществом использования такого подхода — случаи, когда в более комплексных компонентах, с методом или вычисляемым свойством, вы захотите имитировать (mock) или создавать заглушку (stub) в каждом тесте, тогда вам нужно будет объявить его только один раз.

Дополнительный контекст

Вышеприведённый тест довольно прост, но на практике у Vue-компонентов часто другое поведение, которые вы хотите проверить, например:

Более подробные примеры, показывающие такие тесты в действии, вы можете найти в руководствах.

Vue Test Utils и огромная экосистема JavaScript обеспечивают множество инструментов чтобы обеспечить почти 100% покрытия тестами. Однако модульные тесты являются лишь частью пирамиды тестирования. Есть и другие типы тестов, включая тесты e2e (end-to-end) и тесты снимками (snapshot). Модульные тесты — это самые маленькие и самые простые тесты — они проверяет утверждения на маленьких единицах работы, изолируя каждую часть единственного компонента.

Тесты снимками сохраняют разметку вашего Vue-компонента и сравнивают с новой, создаваемой при каждом выполнении теста. Если что-то изменяется, разработчик будет в курсе и сможет решить, было ли решение намеренным (компонент был обновлён) или случайным (компонент работает некорректно).

Тесты end-to-end гарантирует, что несколько компонентов хорошо взаимодействуют друг с другом. Они находятся на более высоком уровне. Примерами таких тестов может быть тестирование регистрации, входа и изменения данных у пользователя. Они работают медленнее, чем модульные тесты или тестирование снимками.

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

Тесты высокого уровня, такие как end-to-end тесты, работают намного медленнее. Обычно их запускают перед публикацией сайта для гарантии, что каждая часть системы работает корректно.

Более подробную информацию о тестировании Vue-компонентов вы можете найти в книге Testing Vue.js Applications от члена команды Эдда Йербурга.

Когда не следует тестировать

Модульное тестирование — важная часть любого серьёзного приложения. Сначала, когда видение развития приложения остаётся неясным, модульное тестирование может замедлить разработку, но после стабилизации направления развития и взаимодействия с настоящими пользователями, модульные тесты (и другие типы автоматических тестов) абсолютно необходимы для обеспечения удобства в поддержке и масштабируемости кодовой базы.