Это руководство было написано для Vue.js 3 и Vue Test Utils v2.
Версия для Vue.js 2 здесь.
# Тестирование действий
Тестировать действия в изоляции очень просто. Это очень похоже на тестирование мутаций в изоляции – смотрите здесь про тестирование мутаций. Тестирование диспетчеризации действий в контексте компонента обсуждалось здесь.
Исходный код для теста на этой странице можно найти здесь.
# Создание действия
Мы напишем действие, которое следует обычному Vuex паттерну:
- делаем асинхронный запрос к API
- обрабатываем данные (опционально)
- делаем
commit
мутации с результатом в виде нагрузки
Вот действие authenticate
, которое отправляет имя пользователя и пароль к внешнему API, в котором проверяется совпадают ли они. Затем результат используется для обновления хранилища, путём выполнения мутации SET_AUTHENTICATED
с результатом в виде нагрузки.
import axios from "axios"
export default {
async authenticate({ commit }, { username, password }) {
const authenticated = await axios.post("/api/authenticate", {
username, password
})
commit("SET_AUTHENTICATED", authenticated)
}
}
Тест для действия должен проверять:
- правильный ли endpoint API использовался?
- правильная ли нагрузка для мутации?
- правильная ли мутация была использована?
Давайте двигаться вперёд и напишем тест, пусть ошибки подсказывают нам что делать.
# Написание теста
describe("Авторизация", () => {
it("авторизует пользователя", async () => {
const commit = jest.fn()
const username = "alice"
const password = "password"
await actions.authenticate({ commit }, { username, password })
expect(url).toBe("/api/authenticate")
expect(body).toEqual({ username, password })
expect(commit).toHaveBeenCalledWith(
"SET_AUTHENTICATED", true)
})
})
Так как axios
асинхронный, нужно убедиться, что Jest дождётся окончания теста. Добавим async
для функции и await
для вызова actions.authenticate
. В противном случае тест завершится до проверки expect
, и мы получим тест, который всегда будет проходить проверку.
Запуск теста выше выдаст нам такую ошибку:
FAIL tests/unit/actions.spec.js
● Авторизация › авторизует пользователя
SyntaxError: The string did not match the expected pattern.
at XMLHttpRequest.open (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:482:15)
at dispatchXhrRequest (node_modules/axios/lib/adapters/xhr.js:45:13)
at xhrAdapter (node_modules/axios/lib/adapters/xhr.js:12:10)
at dispatchRequest (node_modules/axios/lib/core/dispatchRequest.js:59:10)
Это ошибка возникает где-то внутри axios
. Мы делаем запрос к /api...
и, так как мы запускаем тест в тестовом окружении, в котором даже нет сервера, получаем ошибку. Мы также не определили url
или body
– мы сделаем это, когда будем решать ошибку с axios
.
Так как мы используем Jest, можно легко замокать вызов API, применяя jest.mock
. Мы сделаем мок для axios
, который даст нам больше контроля над его поведением. Jest предоставляет моки для ES6 классов, которые идеально подходят для мока axios
.
Мок для axios
выглядит следующим образом:
let url = ''
let body = {}
jest.mock("axios", () => ({
post: (_url, _body) => {
return new Promise((resolve) => {
url = _url
body = _body
resolve(true)
})
}
}))
Мы сохраняем url
и body
в переменные, чтобы мы могли проверить, что правильный endpoint получает правильную нагрузку. Так как мы не хотим использовать настоящий endpoint, мы немедленно выполняем промис, что симулирует успешный вызов API.
yarn test:unit
теперь выводит сообщение, что тест прошёл проверку!
# Тестирование ошибки API
Мы только протестировали случай, когда вызов API успешный. Важно тестировать все возможные случаи. Давайте напишем тест, в котором происходит ошибка. В этот раз, мы сначала напишем тест, а затем — реализацию.
Тест выглядит примерно так:
it("ловит ошибки", async () => {
mockError = true
await expect(actions.authenticate({ commit: jest.fn() }, {}))
.rejects.toThrow("Произошла ошибка API.")
})
Нам нужно найти способ заставить мок для axios
сгенерировать ошибку. Вот для чего нужна переменная mockError
. Обновим мок для axios
вот так:
let url = ''
let body = {}
let mockError = false
jest.mock("axios", () => ({
post: (_url, _body) => {
return new Promise((resolve) => {
if (mockError)
throw Error()
url = _url
body = _body
resolve(true)
})
}
}))
Jest позволяет обращаться к переменной вне текущего лексического окружения ES6 класса только, если перед названием переменной стоит mock
. Теперь мы можем с лёгкостью сделать mockError = true
и axios
сгенерирует ошибку.
Запуск теста выдаст нам такую ошибку:
FAIL tests/unit/actions.spec.js
● Авторизация › ловит ошибки
expect(function).toThrow(string)
Expected the function to throw an error matching:
"Произошла ошибка API."
Instead, it threw:
Mock error
Он успешно словил ошибку... но не ту которую мы хотели. Обновим authenticate
, чтобы генерировать ошибку, которая ожидается в тесте:
export default {
async authenticate({ commit }, { username, password }) {
try {
const authenticated = await axios.post("/api/authenticate", {
username, password
})
commit("SET_AUTHENTICATED", authenticated)
} catch (e) {
throw Error("Произошла ошибка API.")
}
}
}
Теперь тесты проходят проверку.
# Улучшение
Теперь вы знаете, как тестировать действия в изоляции. Есть, как минимум, одно улучшение, которое можно сделать – это реализовать мок axios
в виде пользовательского мока. Необходимо создать папку __mocks__
на том же уровне, где находится node_modules
, и реализовать мок модуль там. Сделав это, вы можете использовать реализацию мока во всех тестах. Jest автоматически использует мок реализации из __mocks__
. На сайте Jest и в интернете есть множество примеров, как сделать это. Рефакторинг этого теста, используя пользовательский мок, остаётся как упражнение для читателя.
# Заключение
В этом руководстве обсудили:
- как использовать моки Jest для ES6 классов
- как тестировать успешные и неудачные случаи действий
Исходный код для теста на этой странице можно найти здесь.