Оглавление
Тестирование
Паттерн AAA
Если посмотреть на юнит тест, то для большинства можно чётко выделить 3 части кода:
- Arrange (настройка) — в этом блоке кода мы настраиваем тестовое окружение тестируемого юнита;
- Act — выполнение или вызов тестируемого сценария;
- Assert — проверка того, что тестируемый вызов ведёт себя определённым образом.
Этот паттерн улучшает структуру кода и его читабельность, однако начинать писать тест нужно всегда с элемента Arrange.
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
describe('Добавляет новую задачу в список', async () => {
/**
* Arrange
*/
const wrapper = mount(TodoApp)
/**
* Act
*/
await wrapper.get('[data-testid="new-todo"]').setValue('New todo')
await wrapper.get('[data-testid="form"]').trigger('submit')
/**
* Assert
*/
expect(wrapper.findAll('[data-testid="todo"]')).toHaveLength(2)
})
Один кейс на один тест
Разделять тесты предпочтительнее, так как мы получаем больше информации о том, что конкретно пошло не так. Помимо этого есть одно хорошее правило, которому стоит следовать.
Один тест проверяет одну вещь.
Если вы посмотрите на тест и увидите в нём две независимые проверки, то такой тест лучше разделить на два более простых.
/**
* 💩 Плохо: в одном тесте множество проверок
*/
describe('moment.js', () => {
it('обрабатывает границы дат', () => {
let date;
date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
/**
* 😊 Хорошо: тест разбит на несколько более мелких
*/
describe('moment.js', () => {
it('корректно добавляет один месяц', () => {
const date = new MakeMomentJSGreatAgain('1/1/2015');
date.addDays(30);
assert.equal('1/31/2015', date);
});
it('корректно работает в високосный год', () => {
const date = new MakeMomentJSGreatAgain('2/1/2016');
date.addDays(28);
assert.equal('02/29/2016', date);
});
it('корректно работает в не високосный год', () => {
const date = new MakeMomentJSGreatAgain('2/1/2015');
date.addDays(28);
assert.equal('03/01/2015', date);
});
});
Что и как тестировать во Vue компоненте?
Перед тестированием компонента надо поставить себя на роль пользователя и работать только с той информацией, которая доступна снаружи:
HTMLemitsslots- вызовы внешних сущностей (
Vuex,API, и т.д.)
Категорически запрещается лезть во внутренности компонента. Мы должны взаимодействовать с компонентом только через публичный интерфейс: изменять пропсы, доводить компонент до нужного состояния, взаимодействуя с HTML.
describe('SBtn.vue', () => {
/**
* 💩 Плохо: лезем во внутренности.
* Название computed поля – личное дело компонента
*/
it('правильно навешивает классы', () => {
const wrapper = mount(SBtn)
expect(wrapper.vm.classes).toContain(['s-btn'])
});
/**
* 😊 Хорошо: работаем с HTML разметкой
*/
it('правильно навешивает классы', () => {
const wrapper = mount(SBtn)
expect(wrapper.classes()).toContain('s-btn')
});
/**
* 💩 Плохо: эмулируем события и лезем во внутренности
*/
it('эмитит событие "click" наверх', () => {
const wrapper = mount(SBtn)
const vm = wrapper.vm
vm.onClick(event)
const clickEvent = wrapper.emitted().click
expect(clickEvent[0][0]).toEqual(true)
});
/**
* 😊 Хорошо: повторяем поведение пользователя
*/
it('эмитит событие "click" наверх', async () => {
const wrapper = mount(SBtn)
await wrapper.find('[data-testid="button"]').trigger('click')
const clickEvent = wrapper.emitted().click
expect(clickEvent[0][0]).toEqual(true)
});
});
shallowMount или mount?
Вместо того чтобы брать что-то одно, мы берём лучшее из двух миров. Для сложных страниц или компонентов, которые имеют много зависимостей, мы используем shallowMount, в остальных случаях mount.
Если смотреть по проектам, то:
@maxus/apps–shallowMount@maxus/modules–shallowMount@maxus/forms–mount@maxus/svztify–mount
Как искать компоненты?
Компоненты следует искать по тому, как их искал бы пользователь, настолько, насколько это возможно. Например, если я хочу отправить форму, я буду искать кнопку, у которой есть текст "Отправить", а не data-testid="submit".
Однако это не значит, что нельзя применять data-testid, ситуации могут быть разные. Атрибут всегда называется data-testid, т.к. он вырезается нашим бандлером в продакшен сборке, чтобы не засорять разметку.
Поиск по классам запрещён, т.к. это про дизайн.
describe('SBtn.vue', () => {
/**
* 💩 Плохо: ищем по классам, ненадёжно
*/
it('имеет правильную разметку', () => {
const wrapper = mount(SBtn)
const el = wrapper.find('.some-selector')
expect(el.html()).toBe('<div data-testid="el">Текст</div>')
});
/**
* 😊 Хорошо: ищем по testid
*/
it('имеет правильную разметку', () => {
const wrapper = mount(SBtn)
const el = wrapper.find('[data-testid="el"]')
expect(el.html()).toBe('<div data-testid="el">Текст</div>')
});
/**
* 😊 Хорошо: ищем по тексту
*/
it('имеет правильную разметку', () => {
const wrapper = createWrapper(within(SBtn))
const el = wrapper.getByText('Текст')
expect(el.html()).toBe('<div>Текст</div>')
});
});