Тестирование JavaScript с помощью Jest

Jest - это библиотека для тестирования кода JavaScript. Это проект с открытым исходным кодом, поддерживаемый Facebook, и он особенно хорошо подходит для тестирования кода React, хотя и не ограничивается этим: он может тестировать любой код JavaScript. Jest очень быстрый и простой в использовании

Введение в Jest

Jest - это библиотека для тестирования кода JavaScript.

Это проект с открытым исходным кодом, поддерживаемый Facebook, и он особенно хорошо подходит для тестирования кода React, хотя и не ограничивается этим: он может тестировать любой код JavaScript. Его сильные стороны:

  • это быстро
  • он может выполнятьтестирование снимков
  • он самоуверен и предоставляет все "из коробки", не требуя от вас делать выбор

Jest - это инструмент, очень похожий на Mocha, хотя у них есть отличия:

  • Mocha менее самоуверен, в то время как Jest придерживается определенного набора условностей.
  • Mocha требует дополнительной настройки, в то время как Jest обычно работает из коробки, благодаря своей самоуверенности.
  • Mocha старше и более устоявшийся, с большим количеством интеграций инструментов

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

Установка

Jest автоматически устанавливается вcreate-react-app, поэтому, если вы его используете, вам не нужно устанавливать Jest.

Jest можно установить в любом другом проекте, используяПряжа:

yarn add --dev jest

или жеnpm:

npm install --save-dev jest

обратите внимание, как мы приказываем обоим поместить Jest вdevDependenciesчастьpackage.jsonфайл, так что он будет установлен только в среде разработки, а не в производственной среде.

Добавьте эту строку в часть скриптов вашегоpackage.jsonфайл:

{
  "scripts": {
    "test": "jest"
  }
}

так что тесты можно запускать с помощьюyarn testили жеnpm run test.

В качестве альтернативы вы можете установить Jest глобально:

yarn global add jest

и запустите все свои тесты, используяjestинструмент командной строки.

Создайте первый тест Jest

Проекты, созданные сcreate-react-appиметь установленный и предварительно настроенный Jest из коробки, но добавить Jest в любой проект так же просто, как ввести

yarn add --dev jest

Добавить в свойpackage.jsonэта строка:

{
  "scripts": {
    "test": "jest"
  }
}

и запустите свои тесты, выполнивyarn testв вашей оболочке.

Теперь у вас нет никаких тестов, поэтому ничего не будет выполняться:

Testing with Yarn

Создадим первый тест. Откройтеmath.jsfile и введите пару функций, которые мы позже протестируем:

const sum = (a, b) => a + b
const mul = (a, b) => a * b
const sub = (a, b) => a - b
const div = (a, b) => a / b

module.exports = { sum, mul, sub, div }

Теперь создайтеmath.test.jsфайл в той же папке, и там мы будем использовать Jest для тестирования функций, определенных вmath.js:

const { sum, mul, sub, div } = require('./math')

test(‘Adding 1 + 1 equals 2’, () => { expect(sum(1, 1)).toBe(2) }) test(‘Multiplying 1 * 1 equals 1’, () => { expect(mul(1, 1)).toBe(1) }) test(‘Subtracting 1 - 1 equals 0’, () => { expect(sub(1, 1)).toBe(0) }) test(‘Dividing 1 / 1 equals 1’, () => { expect(div(1, 1)).toBe(1) })

Бегyarn testприводит к тому, что Jest запускается для всех найденных тестовых файлов и возвращает нам конечный результат:

Passing tests

Запустите Jest с VS Code

Visual Studio Code - отличный редактор для разработки на JavaScript. ВРасширение Jestпредлагает первоклассную интеграцию для наших тестов.

После его установки он автоматически определит, установили ли вы Jest в свои devDependencies, и запустит тесты. Вы также можете вызвать тесты вручную, выбравJest: Start Runnerкоманда. Он будет запускать тесты и оставаться в режиме просмотра, чтобы повторно запускать их всякий раз, когда вы изменяете один из файлов, содержащих тест (или тестовый файл):

A simple Jest test running in VS Code

Матчеры

В предыдущей статье я использовалtoBe()как единственныйсопоставитель:

test('Adding 1 + 1 equals 2', () => {
  expect(sum(1, 1)).toBe(2)
})

Сопоставитель - это метод, который позволяет вам проверять значения.

Наиболее часто используемые сопоставители, сравнивающие значение результатаexpect()со значением, переданным в качестве аргумента, следующие:

  • toBeсравнивает строгое равенство, используя===
  • toEqualсравнивает значения двух переменных. Если это объект или массив, он проверяет равенство всех свойств или элементов.
  • toBeNullистинно при передаче нулевого значения
  • toBeDefinedистинно при передаче определенного значения (противоположно указанному выше)
  • toBeUndefinedистинно при передаче неопределенного значения
  • toBeCloseToиспользуется для сравнения значений с плавающей запятой, избегая ошибок округления
  • toBeTruthyистина, если значение считается истинным (например,ifделает)
  • toBeFalsyистина, если значение считается ложным (например,ifделает)
  • toBeGreaterThanистина, если результат expect () выше аргумента
  • toBeGreaterThanOrEqualистина, если результат expect () равен аргументу или больше аргумента
  • toBeLessThanистина, если результат expect () ниже аргумента
  • toBeLessThanOrEqualистина, если результат expect () равен аргументу или меньше аргумента
  • toMatchиспользуется для сравнения строк срегулярное выражениесопоставление с образцом
  • toContainиспользуется в массивах, истина, если ожидаемый массив содержит аргумент в его наборе элементов
  • toHaveLength(number): проверяет длину массива
  • toHaveProperty(key, value): проверяет, есть ли у объекта свойство, и при необходимости проверяет его значение
  • toThrowпроверяет, вызывает ли переданная вами функция исключение (в общем) или конкретное исключение
  • toBeInstanceOf(): проверяет, является ли объект экземпляром класса

Все эти сопоставители можно отменить, используя.not.внутри оператора, например:

test('Adding 1 + 1 does not equal 3', () => {
  expect(sum(1, 1)).not.toBe(3)
})

Для использования с обещаниями вы можете использовать.resolvesи.rejects:

expect(Promise.resolve('lemon')).resolves.toBe('lemon')

expect(Promise.reject(new Error(‘octopus’))).rejects.toThrow(‘octopus’)

Настраивать

Перед запуском тестов вы захотите выполнить некоторую инициализацию.

Чтобы сделать что-то один раз перед запуском всех тестов, используйтеbeforeAll()функция:

beforeAll(() => {
  //do something
})

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

beforeEach(() => {
  //do something
})

Срывать

Как и в случае с настройкой, вы также можете выполнять что-то после каждого запуска теста:

afterEach(() => {
  //do something
})

и после окончания всех тестов:

afterAll(() => {
  //do something
})

Групповые тесты с использованием description ()

Вы можете создавать группы тестов в одном файле, которые изолируют функции настройки и удаления:

describe('first set', () => {
  beforeEach(() => {
    //do something
  })
  afterAll(() => {
    //do something
  })
  test(/*...*/)
  test(/*...*/)
})

describe(‘second set’, () => { beforeEach(() => { //do something }) beforeAll(() => { //do something }) test(//) test(//) })

Тестирование асинхронного кода

Асинхронный код в современном JavaScript может иметь две формы: обратные вызовы и обещания. Помимо обещаний, мы можем использовать async / await.

Обратные вызовы

У вас не может быть теста в обратном вызове, потому что Jest не выполнит его - выполнение тестового файла заканчивается до того, как вызывается обратный вызов. Чтобы исправить это, передайте параметр тестовой функции, которую вы можете легко вызватьdone. Шутка подождет, пока ты позвонишьdone()перед окончанием этого теста:

//uppercase.js
function uppercase(str, callback) {
  callback(str.toUpperCase())
}
module.exports = uppercase

//uppercase.test.js const uppercase = require(’./src/uppercase’)

test(uppercase 'test' to equal 'TEST', (done) => { uppercase(‘test’, (str) => { expect(str).toBe(‘TEST’) done() } })

Jest async test callback

Обещания

С функциями, возвращающими обещания, мывернуть обещаниеиз теста:

//uppercase.js
const uppercase = str => {
  return new Promise((resolve, reject) => {
    if (!str) {
      reject('Empty string')
      return
    }
    resolve(str.toUpperCase())
  })
}
module.exports = uppercase

//uppercase.test.js const uppercase = require(’./uppercase’) test(uppercase 'test' to equal 'TEST', () => { return uppercase(‘test’).then(str => { expect(str).toBe(‘TEST’) }) })

Jest async test promises

Отклоненные обещания можно проверить с помощью.catch():

//uppercase.js
const uppercase = str => {
  return new Promise((resolve, reject) => {
    if (!str) {
      reject('Empty string')
      return
    }
    resolve(str.toUpperCase())
  })
}

module.exports = uppercase

//uppercase.test.js const uppercase = require(’./uppercase’)

test(uppercase 'test' to equal 'TEST', () => { return uppercase(’’).catch(e => { expect(e).toMatch(‘Empty string’) }) })

Jest async test catch

Асинхронный / ожидание

Для тестирования функций, возвращающих обещания, мы также можем использовать async / await, что делает синтаксис очень простым и понятным:

//uppercase.test.js
const uppercase = require('./uppercase')
test(`uppercase 'test' to equal 'TEST'`, async () => {
  const str = await uppercase('test')
  expect(str).toBe('TEST')
})

Jest async test await async

Издевательство

При тестированиинасмешливыйпозволяет тестировать функциональность, зависящую от:

  • База данных
  • СетьЗапросы
  • доступ кФайлы
  • любойВнешнийсистема

так что:

  1. ваши тесты запускаютсяБыстрее, что позволяет быстро обойтись во время разработки
  2. ваши тестынезависимыйсостояния сети или состояния базы данных
  3. ваши тесты незагрязнятьлюбое хранилище данных, потому что они не касаются базы данных
  4. любое изменение, сделанное в тесте, не изменяет состояние для последующих тестов, и повторный запуск набора тестов должен начинаться с известной и воспроизводимой начальной точки.
  5. вам не нужно беспокоиться об ограничении скорости вызовов API и сетевых запросов

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

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

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

  • expect().toHaveBeenCalled(): проверьте, была ли вызвана шпионская функция
  • expect().toHaveBeenCalledTimes(): подсчитать, сколько раз вызывалась шпионская функция
  • expect().toHaveBeenCalledWith(): проверить, вызывалась ли функция с определенным набором параметров
  • expect().toHaveBeenLastCalledWith(): проверить параметры последнего вызова функции

Шпионские пакеты, не влияющие на код функций

Когда вы импортируете пакет, вы можете указать Jest «шпионить» за выполнением определенной функции, используяspyOn(), не влияя на то, как этот метод работает.

Пример:

const mathjs = require('mathjs')

test(The mathjs log function, () => { const spy = jest.spyOn(mathjs, ‘log’) const result = mathjs.log(10000, 10)

expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

Имитация всего пакета

Jest предоставляет удобный способ имитировать весь пакет. Создать__mocks__в корне проекта, и в этой папке создайте по одному файлу JavaScript для каждого из ваших пакетов.

Скажите, что вы импортируетеmathjs. Создать__mocks__/mathjs.jsфайл в корне вашего проекта и добавьте это содержимое:

module.exports = {
  log: jest.fn(() => 'test')
}

Это будет имитировать функцию пакета log (). Добавьте столько функций, сколько хотите имитировать:

const mathjs = require('mathjs')

test(The mathjs log function, () => { const result = mathjs.log(10000, 10) expect(result).toBe(‘test’) expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

Имитация единственной функции

Вы можете издеваться над одной функцией, используяjest.fn():

const mathjs = require('mathjs')

mathjs.log = jest.fn(() => ‘test’) test(The mathjs log function, () => { const result = mathjs.log(10000, 10) expect(result).toBe(‘test’) expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

Вы также можете использоватьjest.fn().mockReturnValue('test')чтобы создать простой макет, который ничего не делает, кроме возврата значения.

Готовые макеты

Вы можете найти готовые макеты для популярных библиотек. Например этот пакетhttps://github.com/jefflau/jest-fetch-mockпозволяет высмеиватьfetch()вызовы и предоставить образцы возвращаемых значений без взаимодействия с реальным сервером в ваших тестах.

Тестирование снимков

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

Это простой тест на компоненте приложения простогоcreate-react-appприложение (убедитесь, что вы установилиreact-test-renderer):

import React from 'react'
import App from './App'
import renderer from 'react-test-renderer'

it(‘renders correctly’, () => { const tree = renderer.create(<App />).toJSON() expect(tree).toMatchSnapshot() })

при первом запуске этого теста Jest сохраняет снимок в__snapshots__папка. Вот что содержит App.test.js.snap:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
<div
  className="App"
>
  <header
    className="App-header"
  >
    <img
      alt="logo"
      className="App-logo"
      src="logo.svg"
    />
    <h1
      className="App-title"
    >
      Welcome to React
    </h1>
  </header>
  <p
    className="App-intro"
  >
    To get started, edit
    <code>
      src/App.js
    </code>
     and save to reload.
  </p>
</div>
`

Как видите, это код, который визуализирует компонент приложения, не более того.

В следующий раз, когда тест сравнит вывод<App />к этому. Если приложение изменится, вы получите сообщение об ошибке:

Error with snapshot

Когда используешьyarn testвcreate-react-appты врежим часов, и оттуда вы можете нажатьwи показать больше вариантов:

Watch Usage
 › Press u to update failing snapshots.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.

If your change is intended, pressing u will update the failing snapshots, and make the test pass.

You can also update the snapshot by running jest -u (or jest --updateSnapshot) outside of watch mode.


More devtools tutorials: