Тестирование Vue.js | Компоненты с тестовыми утилитами Vue

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

Vue Test Utils (VTU) - это набор служебных функций, направленных на упрощение тестирования Vue. js компоненты. Он предоставляет методы для изолированного подключения компонентов Vue и взаимодействия с ними. После перехода с Vue 2 на Vue 3 появилась новая версия Vue Test Utils специально для Vue 3.

В этой статье мы создадим пример приложения, используя подход "сначала тестирование". Мы рассмотрим некоторые варианты использования Vue Test Utils для тестирования компонентов Vue и как написать простой в тестировании компонент с помощью Vue Test Utils.

Что такое Vue Test Utils

Согласно документации, Vue Test Utils (VTU) - это набор вспомогательных функций ns разработан для упрощения тестирования Vue. js компоненты. Эта библиотека предоставляет метод для установки компонентов Vue и взаимодействия с ними изолированным образом. Эти методы можно назвать оболочкой. По сути, оболочка - это абстракция смонтированного компонента. Он предоставляет некоторые полезные функции, которые упрощают тестирование.

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

Настройка нашей тестовой среды

Чтобы начать, создайте новую папку и инициализируйте Vue. js спроектируйте, выполнив приведенную ниже команду:

npm init vue@latest

Приведенная выше команда установит и выполнит create-vue, официальный инструмент для создания каркасов проекта Vue. Затем он покажет подсказки для нескольких функций установки. Выберите следующие параметры ниже:

✔ Project name: ... <your-project-name>
✔ Add TypeScript? ... No / Yes
✔ Add JSX Support? ... No / Yes
✔ Add Vue Router for Single Page Application development? ... No / Yes
✔ Add Pinia for state management? ... No / Yes
✔ Add Vitest for Unit testing? ... No / Yes
✔ Add Cypress for both Unit and End-to-End testing? ... No / Yes
✔ Add ESLint for code quality? ... No / Yes
✔ Add Prettier for code formatting? ... No / Yes

Выберите "Да" для параметров "ESLint", "Prettier" и "Поддержка JSX" и "Нет" для остальных. Затем запустите следующую команду для установки зависимостей и запуска сервера разработки:

$ cd <your-project-name>
$ npm install
$ npm run dev

Теперь, чтобы добавить тестовые утилиты Vue, запустите следующую команду:

$ npm install --save-dev @vue/test-utils

# or

$ yarn add --dev @vue/test-utils

Vue Test Utils, по сути, не зависит от фреймворка - вы можете использовать его с любым тестировщиком, который вам нравится. Самый простой способ опробовать это - использовать Jest, популярный тестовый раннер, который мы будем использовать для этого урока. Чтобы загрузить файлы. vue с помощью Jest, вам понадобится vue-jest.

Чтобы установить vue-jest, выполните следующую команду:

$ npm install --save-dev vue-jest

Затем установите Jest:

$ npm install --save-dev jest

Создайте новый файл в папке вашего проекта под названием jest. config. js и добавьте следующий код:

module.exports = {
  preset: "ts-jest",
  globals: {},
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.vue$": "vue-jest",
    "^.+\\js$": "babel-jest",
  },
  moduleFileExtensions: ["vue", "js", "json", "jsx", "ts", "tsx", "node"],
};

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

Пишем свой первый тест

Давайте рассмотрим, как использовать Vue Test Utils, написав тест для простого демонстрационного приложения. Откройте App. vue и создайте демонстрационный компонент todo:

<template>
  <div></div>
</template>

<script lang="ts">
export default {
  name: "TodoApp",

  data() {
    return {
      todos: [
        {
          id: 1,
          text: "Go to the grocery store",
          completed: false,
        },
      ],
    };
  },
};
</script>

В приведенном выше тесте мы используем монтирование из VTU для рендеринга компонента. Затем мы вызываем mount и передаем компонент в качестве первого аргумента. По сути, мы находим элемент с селектором data-test="todo" - в DOM, это будет выглядеть как

import { mount } from "@vue/test-utils";
import App from "./App.vue";

test("renders a todo", () => {
  const wrapper = mount(App);

  const todo = wrapper.get('[data-test="todo"]');

  expect(todo.text()).toBe("Go to the grocery store");
});

После этого давайте напишем наш первый тест, чтобы убедиться, что todo отрисовано. Откройте App.spec.ts и добавьте следующий тест:

import { mount } from "@vue/test-utils";
import App from "./App.vue";

test("renders a todo", () => {
  const wrapper = mount(App);

  const todo = wrapper.get('[data-test="todo"]');

  expect(todo.text()).toBe("Go to the grocery store");
});

В приведенном выше тесте мы используем монтирование из VTU для рендеринга компонента. Затем мы вызываем mount и передаем компонент в качестве первого аргумента. По сути, мы находим элемент с селектором data-test="todo" - в DOM, это будет выглядеть как <div data-test="todo">...</div>. Далее мы вызываем текстовый метод, чтобы получить содержимое, которое, как мы ожидаем, будет "Сходить в продуктовый магазин".

На данный момент, если мы запустим тест, он завершится неудачей со следующим сообщением об ошибке:

  FAIL  src/App.spec.ts
  ✕ renders a todo (13ms)

  ● renders a todo
  
     Unable to get [data-test="todo"] within: <div></div>

Тест завершился неудачей, потому что мы не отображаем ни одного элемента todo, поэтому вызову get() не удается вернуть оболочку. Теперь давайте обновим <template> в App.vue, чтобы отобразить массив todos:

<template>
  <div>
    <div v-for="todo in todos" :key="todo.id" data-test="todo">
      {{ todo.text }}
    </div>
  </div>
</template>

С учетом этого изменения, приведенного выше, тест будет пройден:

PASS  src/App.spec.ts
  ✓ renders a todo (24ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.092s
Ran all test suites.
✨  Done in 5.51s.

Вы только что успешно написали свой первый компонентный тест.

В качестве следующего шага мы добавим возможность для пользователя создавать новые задачи. Чтобы достичь этого, нам нужна форма с возможностью ввода пользователем некоторого текста. Поэтому, когда пользователь отправляет форму, мы ожидаем, что todo будет отображен. Чтобы сделать это, взгляните на приведенный ниже тест:

import { mount } from "@vue/test-utils";
import App from "./App.vue";

test("creates a todo", async () => {
  const wrapper = mount(App);

  await wrapper.get('[data-test="new-todo"]').setValue("New todo");
  await wrapper.get('[data-test="form"]').trigger("submit");

  expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2);
});

В приведенном выше тесте мы используем метод монтирования для рендеринга элемента. Мы также утверждали, что отображается только 1 задача - это дает лучшее разъяснение того, что мы добавляем дополнительную задачу, как следует из последней строки теста. Чтобы обновить входные данные, мы используем метод setValue - это позволяет нам установить значение входных данных.

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

<template>
  <div>
    <div v-for="todo in todos" :key="todo.id" data-test="todo">
      {{ todo.text }}
    </div>

    <form data-test="form" @submit.prevent="createTodo">
      <input data-test="new-todo" v-model="newTodo" />
    </form>
  </div>
</template>

<script lang="ts">
export default {
  name: "TodoApp",

  data() {
    return {
      newTodo: "",
      todos: [
        {
          id: 1,
          text: "Go to the grocery store",
          completed: false,
        },
      ],
    };
  },

  methods: {
    createTodo() {
      this.todos.push({
        id: 2,
        text: this.newTodo,
        completed: false,
      });
    },
  },
};
</script>

В приведенном выше коде мы используем v-модель для привязки к входным данным и @submit для прослушивания отправки формы. Когда форма отправлена, вызывается createTodo и вставляет новый todo в массив todos.

PASS  src/App.spec.ts
  ✓ creates a todo (19ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.185s, estimated 2s
Ran all test suites.
✨  Done in 4.71s.

Условный рендеринг

VTU (Vue Test Utils) предоставляет множество функций для рендеринга и подтверждения состояния компонента, чтобы гарантировать его корректное поведение. Далее давайте рассмотрим, как визуализировать компоненты, а также как обеспечить правильную визуализацию их содержимого.

Ключевой особенностью Vue является его динамическая вставка/ удаление элементов с помощью v-if. Итак, давайте посмотрим, как протестировать компонент, использующий v-if:

const Nav = {
  template: `
  <nav>
     <a id="user-profile" href="/profile">User Profile</a>
     <a v-if="subscribed" id="subscribed" href="/dashboard">User Dashboard</a>
  </nav>
    `,

  data() {
    return {
      subscribed: false,
    };
  },
};

В приведенном выше компоненте <Nav> отображается ссылка на профиль пользователя. Между тем, если значение подписки равно true, мы покажем ссылку на пользовательскую панель мониторинга. Итак, есть три сценария, которые мы должны проверить на правильность функционирования:

  • Должна быть показана ссылка на /profile.
  • Когда пользователь подписан, должна быть показана ссылка /dashboard.
  • Если пользователь не подписан, ссылка /dashboard не должна отображаться.

В оболочке Vue Test Utils есть метод get(), который выполняет поиск существующего элемента. Он использует синтаксис querySelector. Мы назначаем содержимое ссылки на пользовательскую панель мониторинга с помощью функции get():

test("renders the user dashboard link", () => {
  const wrapper = mount(Nav);

  const userProfileLink = wrapper.get("#user-profile");
  expect(userProfileLink.text()).toEqual("User Profile");
});

Возможно, если метод get() не вернет элемент, соответствующий селектору, он выдаст ошибку, и ваш тест завершится неудачей. Метод get() возвращает DOMWrapper, если элемент найден.

Однако метод get работает в предположении, что элементы действительно существуют, и выдает ошибку, когда их нет. Не рекомендуется использовать его для утверждения существования. Таким образом, Vue Test Utils предоставляет нам методы find и exists. Следующий тест утверждает, что если значение subscribed равно false (что по умолчанию так и есть), ссылка на пользовательскую панель мониторинга недоступна:

test("does not render user dashboard link", () => {
  const wrapper = mount(Nav);

  // Using `wrapper.get` would throw and make the test fail.
  expect(wrapper.find("#subscribed").exists()).toBe(false);
});

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

Как протестировать генерируемые события

В Vue компоненты взаимодействуют друг с другом через props и путем отправки событий путем вызова $emit. Теперь давайте взглянем на то, как проверить правильность отправки событий с помощью функции emissed(), предоставляемой Vue Test Utils.

Давайте рассмотрим этот простой компонент счетчика ниже. В нем есть кнопка, которая при нажатии увеличивает внутреннюю переменную count и выдает ее значение:

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    handleClick() {
      this.count += 1;
      this.$emit("increment", this.count);
    },
  },
};

Чтобы протестировать описанный выше компонент, мы должны убедиться, что генерируется событие increment с последним значением count.

Чтобы сделать это, мы будем зависеть от метода emissed(). Он вернет объект со всеми событиями, которые были сгенерированы компонентом, и их аргументами в массиве. Давайте посмотрим, как это работает:

test("emits an event when clicked", () => {
  const wrapper = mount(Counter);

  wrapper.find("button").trigger("click");
  wrapper.find("button").trigger("click");

  expect(wrapper.emitted()).toHaveProperty("increment");
});

В приведенном выше тесте первое, что вы заметите, это то, что функция emissed() возвращает объект, где каждый ключ соответствует генерируемому событию. В этом случае увеличьте значение.

Этот тест должен пройти сейчас. Мы убедились, что запустили событие с правильным названием. Если вы раньше не видели метод trigger(), не волнуйтесь; он используется для имитации взаимодействия с пользователем. Вы можете узнать об утверждении сложных событий здесь.

Как тестировать формы

В этом разделе мы рассмотрим способы взаимодействия с элементами формы, установки значений и запуска событий.

Давайте взглянем на эту базовую форму ниже:

<template>
  <div>
    <input type="email" v-model="email" />

    <button @click="submit">Submit</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: "",
    };
  },
  methods: {
    submit() {
      this.$emit("submit", this.email);
    },
  },
};
</script>

Одним из наиболее распространенных способов привязки и ввода данных в Vue является использование v-модели. Итак, чтобы изменить значение ввода в VTU, вы можете использовать метод setValue. Он принимает параметр, чаще всего строковый или логическое значение, и возвращает обещание, которое разрешается после того, как Vue обновит DOM.

test("sets the value", async () => {
  const wrapper = mount(Component);
  const input = wrapper.find("input");

  await input.setValue("david@mail.com");

  expect(input.element.value).toBe("david@mail.com");
});

В приведенном выше тесте вы можете видеть, что setValue устанавливает свойство value для входных элементов в соответствие с тем, что мы ему передаем. Мы используем await, чтобы убедиться, что Vue завершил обновление и изменение было отражено в DOM, прежде чем мы сделаем какие-либо утверждения.

Запуск событий - это второе по важности действие при работе с формами и элементами действий. Теперь давайте взглянем на нашу кнопку из предыдущего примера.

<button @click="submit">Submit</button>

Чтобы вызвать событие click, мы можем использовать метод trigger().

test("trigger", async () => {
  const wrapper = mount(Component);

  // Trigger the element
  await wrapper.find("button").trigger("click");

  // Assert some action has been performed, like an emitted event.
  expect(wrapper.emitted()).toHaveProperty("submit");
});

В приведенном выше тесте мы запускаем прослушиватель событий click, чтобы компонент выполнил метод отправки. Как и в случае с setValue, мы используем await, чтобы убедиться, что действие отражается Vue. Тогда мы можем утверждать, что произошло какое-то действие. В этом случае мы выдали правильное событие.

Итак, давайте объединим эти два метода, чтобы проверить, выдает ли наша простая форма пользовательские данные:

test("emits the input to its parent", async () => {
  const wrapper = mount(Component);

  // Set the value
  await wrapper.find("input").setValue("david@mail.com");

  // Trigger the element
  await wrapper.find("button").trigger("click");

  // Assert the `submit` event is emitted,
  expect(wrapper.emitted("submit")[0][0]).toBe("david@mail.com");
});

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

Утилиты тестирования Vue помогают вам писать тесты для компонентов Vue; однако VTU может сделать не так уж много. Вам всегда нужно писать код, который легче тестировать, и писать тесты, которые имеют смысл и просты в обслуживании. Вы можете следовать этому руководству о том, как писать компоненты, которые легко тестировать:

Не тестируйте детали реализации: всегда думайте о входных и выходных данных с точки зрения пользователя. Входные данные – такие как взаимодействие, реквизит, потоки данных, и выходные данные, такие как элементы DOM, события и побочные эффекты.

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

Напишите тест перед написанием компонента: Если вы напишете тест заранее, вы не сможете написать непроверяемый код. Это одно из самых больших преимуществ написания тестов перед написанием компонентов.

Вывод

Мы надеюся, вам понравилось читать этот пост. Мы изучили тестовые утилиты Vue, специально созданные для Vue3. Мы продемонстрировали некоторые варианты использования утилит тестирования Vue для тестирования компонентов Vue и рассмотрели, как настроить среду тестирования с помощью VTU, как выполнять условный рендеринг и как тестировать генерируемые события и элементы формы.