Поскольку приложения становятся более сложными, чем когда-либо, важно тестировать их автоматически. Мы можем сделать это с помощью модульных тестов, и тогда нам не придется все тестировать вручную.

В этой статье мы рассмотрим, как тестировать приложения Vue.js, написав простое приложение и протестировав его.

Начиная

Для начала мы создаем приложение, которое берет шутку из API шуток Чака Норриса.

Начнем с создания пустой папки, входа в нее и запуска Vue CLI, запустив:

npx vue create .

В мастере выбираем Модульные тесты, затем выбираем Jest и продолжаем.

Теперь, когда у нас есть сгенерированные файлы, мы можем изменить код. Мы можем удалить папку components и заменить код в App.vue на:

<template>
  <div id="app">
    <button @click='toggleJoke()'>{{jokeHidden ? 'Show' : 'Hide'}} Joke</button>
    <p v-if="!jokeHidden">{{data.value.joke}}</p>
  </div>
</template>
<script>
export default {
  name: "app",
  data() {
    return {
      jokeHidden: false,
      data: { value: {} }
    };
  },
  beforeMount() {
    this.getJoke();
  },
  methods: {
    async getJoke() {
      const res = await fetch("http://api.icndb.com/jokes/random");
      this.data = await res.json();
    },
    toggleJoke() {
      this.jokeHidden = !this.jokeHidden;
    }
  }
};
</script>
<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Код просто получает шутку от API и затем отображает ее. Кроме того, у него есть кнопка, чтобы показать и скрыть шутку.

Наше приложение выглядит примерно так:

Создание тестов

Теперь, когда у нас есть что тестировать, мы можем писать тесты.

В папке tests/unit мы удаляем то, что у нас есть, а затем создаем app.spec.js в этой папке.

Затем открываем созданный файл и добавляем:

import { mount } from '@vue/test-utils';
import App from '@/App.vue'
const mockResponse = {
  "type": "success",
  "value": {
    "id": 178,
    "joke": "In an act of great philanthropy, Chuck made a generous donation to the American Cancer Society. He donated 6,000 dead bodies for scientific research.",
    "categories": []
  }
}

Чтобы импортировать компонент, который мы будем тестировать, функцию mount, позволяющую Vue Test Utils создавать и отображать компонент для тестирования, и объект mockResponse, который мы будем использовать для установки фиктивных данных.

Затем мы добавляем скелет для нашего теста, написав:

describe('App.vue', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })
})

У нас есть строковое описание для нашего набора тестов и обратный вызов, в который мы добавляем тесты.

Внутри обратного вызова у нас есть ловушка beforeEach, чтобы очистить все имитации, запустив jest.clearAllMocks().

Нам это нужно, потому что позже мы будем имитировать некоторые функции в нашем компоненте.

Добавление нашего первого теста

Далее мы пишем наш первый тест. Этот тест будет имитировать получение данных из API, а затем отображение шутки на экране.

На самом деле он не получит шутку от сервера, поскольку мы хотим, чтобы наш тест запускался где угодно и в любое время. Получение его с сервера не позволит нам этого сделать.

API возвращает что-то новое каждый раз, когда мы его вызываем, и это также может быть не всегда доступно.

Имея это в виду, мы пишем:

it('renders joke', async () => {
    const wrapper = mount(App, {
      methods: {
        getJoke: jest.fn()
      }
    });
    wrapper.vm.data = mockResponse;
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)
  })

в обратном вызове мы передали функцию describe после вызова beforeEach.

Приведенный выше тест вызывает mount нашего App компонента для построения и рендеринга компонента и возвращает объект Wrapper, чтобы мы могли получить к нему доступ.

Во втором аргументе мы передаем параметры с помощью свойства methods, чтобы мы могли имитировать метод getJoke с помощью Jest с jest.fn(). Мы хотим имитировать это, чтобы наш тест не вызывал API.

Когда у нас есть wrapper, мы запускаем:

wrapper.vm.data = mockResponse;

чтобы установить mockResponse данные в свойство data нашего экземпляра компонента.

Как только мы это сделали, мы проверяем, получили ли мы шутку в нашем mockResponse отображении, написав:

expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)

поскольку мы поместили нашу шутку в тег p в нашем компоненте App.

Метод expect и toMatch взяты из Jest.

Написание теста, который взаимодействует с элементами пользовательского интерфейса

Написание теста, который что-то делает с элементами пользовательского интерфейса, такими как кнопки, - это не намного больше работы.

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

it('toggles joke', () => {
    const wrapper = mount(App, {
      methods: {
        getJoke: jest.fn()
      }
    });
    wrapper.vm.data = mockResponse;
    expect(wrapper.find('button').text()).toMatch('Hide Joke');
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);
    wrapper.find('button').trigger('click');
    expect(wrapper.find('button').text()).toMatch('Show Joke');
    expect(wrapper.find('p').exists()).toBe(false);
    wrapper.find('button').trigger('click');
    expect(wrapper.find('button').text()).toMatch('Hide Joke');
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);
  })

Первая часть:

const wrapper = mount(App, {
  methods: {
    getJoke: jest.fn()
  }
});
wrapper.vm.data = mockResponse;

такой же, как и раньше. Мы имитируем getJoke функцию с jest.fn(), чтобы наш тест не вызывал API. Затем установите фиктивные данные.

Далее мы проверяем текст кнопки, написав:

expect(wrapper.find('button').text()).toMatch('Hide Joke');

и что наша шутка показана в элементе p:

expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);

Затем мы нажимаем нашу кнопку, запустив:

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

А затем проверьте текст кнопки и удалил ли элемент p нашей директивой v-if:

expect(wrapper.find('button').text()).toMatch('Show Joke');
expect(wrapper.find('p').exists()).toBe(false);

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

wrapper.find('button').trigger('click');
expect(wrapper.find('button').text()).toMatch('Hide Joke');
expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);

Проведение тестов

Вместе у нас есть следующий тестовый код в app.test.js:

import { mount } from '@vue/test-utils';
import App from '@/App.vue'
const mockResponse = {
  "type": "success",
  "value": {
    "id": 178,
    "joke": "In an act of great philanthropy, Chuck made a generous donation to the American Cancer Society. He donated 6,000 dead bodies for scientific research.",
    "categories": [
]
  }
}
describe('App.vue', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })
  it('renders joke', async () => {
    const wrapper = mount(App, {
      methods: {
        getJoke: jest.fn()
      }
    });
    wrapper.vm.data = mockResponse;
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)
  })
  it('toggles joke', () => {
    const wrapper = mount(App, {
      methods: {
        getJoke: jest.fn()
      }
    });
    wrapper.vm.data = mockResponse;
    expect(wrapper.find('button').text()).toMatch('Hide Joke');
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);
    wrapper.find('button').trigger('click');
    expect(wrapper.find('button').text()).toMatch('Show Joke');
    expect(wrapper.find('p').exists()).toBe(false);
    wrapper.find('button').trigger('click');
    expect(wrapper.find('button').text()).toMatch('Hide Joke');
    expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);
  })
})

Затем запускаем тесты до npm run test:unit.

У нас должно получиться:

PASS  tests/unit/app.spec.js
  App.vue
    √ renders joke (19ms)
    √ toggles joke (11ms)
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.102s
Ran all test suites.

каждый раз, когда мы запускаем наши тесты, так как мы имитировали данные.

Заключение

Vue CLI создает проект со встроенным модульным тестированием, если мы решим его включить. Это экономит нам много работы.

Jest - это простой инструмент для выполнения тестов с множеством функций, таких как насмешки и expect сопоставления, которые мы можем использовать.

Для тестирования компонентов пользовательского интерфейса мы используем объект-оболочку, возвращаемый mount, который имеет визуализированный компонент. Затем мы можем использовать find для поиска в DOM того, что мы хотим искать.

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

Наконец, у нас есть метод exists, чтобы проверить, действительно ли существует искомый элемент.