Использование Mocks для тестирования в JavaScript с Jest

Введение Jest - популярная среда тестирования с открытым исходным кодом для JavaScript. Мы можем использовать Jest для создания имитаций в нашем тесте - объектов, которые заменяют реальные объекты в нашем коде во время его тестирования. В нашей предыдущей серии о методах модульного тестирования с использованием Sinon.js [/ using-stubs-for-testing-in-javascript-with-sinon-js] мы рассказали, как мы можем использовать Sinon.js для заглушки, шпионажа и имитации Node. .js-приложения - особенно HTTP-вызовы. В этой серии мы рассмотрим методы модульного тестирования в Node.js с использованием

Вступление

Jest - популярная среда тестирования с открытым исходным кодом для JavaScript. Мы можем использовать Jest для создания имитаций в нашем тесте - объектов, которые заменяют реальные объекты в нашем коде во время его тестирования.

В нашей предыдущей серии, посвященной методам модульного тестирования с использованием Sinon.js , мы рассмотрели, как мы можем использовать Sinon.js для заглушки, шпионажа и имитации приложений Node.js, особенно HTTP-вызовов.

В этой серии мы рассмотрим методы модульного тестирования в Node.js с использованием Jest . Jest был создан Facebook и хорошо интегрируется со многими библиотеками и фреймворками JavaScript, такими как React, Angular и Vue, и это лишь некоторые из них. Особое внимание уделяется простоте и производительности.

В этой статье мы рассмотрим, что такое имитаторы, а затем сосредоточимся на том, как настроить Jest для приложения Node.js, чтобы имитировать HTTP-вызов в нашем тесте. Затем мы сравним, как мы используем Jest и Sinon для создания макетов для наших программ.

Что такое моки?

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

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

Давайте теперь посмотрим, как мы можем использовать Jest для создания макетов в Node.js.

Настройка Jest в приложении Node.js

В этом руководстве мы настроим приложение Node.js, которое будет выполнять HTTP-вызовы JSON API, содержащего фотографии в альбоме. Jest будет использоваться для имитации вызовов API в наших тестах.

Во-первых, давайте создадим каталог, в котором будут находиться наши файлы, и переместимся в него:

 $ mkdir PhotoAlbumJest && cd PhotoAlbumJest 

Затем давайте инициализируем проект Node с настройками по умолчанию:

 $ npm init -y 

После инициализации проекта мы приступим к созданию index.js в корне каталога:

 $ touch index.js 

Чтобы помочь нам с HTTP-запросами, мы будем использовать Axios .

Настройка Axios

Мы будем использовать axios качестве нашего HTTP-клиента. Axios - это легкий HTTP-клиент на основе обещаний для Node.js, который также может использоваться веб-браузерами. Это делает его подходящим для нашего случая использования.

Давайте сначала установим его:

 $ npm i axios --save 

Перед использованием axios мы создадим файл с именем axiosConfig.js помощью которого мы настроим клиент Axios. Настройка клиента позволяет нам использовать общие настройки для набора HTTP-запросов.

Например, мы можем установить заголовки авторизации для набора HTTP-запросов или, как правило, базовый URL-адрес, который будет использоваться для всех HTTP-запросов.

Создадим файл конфигурации:

 touch axiosConfig.js 

Теперь давайте обратимся к axios и настроим базовый URL:

 const axios = require('axios'); 
 
 const axiosInstance = axios.default.create({ 
 baseURL: 'https://jsonplaceholder.typicode.com/albums' 
 }); 
 
 module.exports = axiosInstance; 

После установки baseURL мы экспортировали axios чтобы использовать его в нашем приложении. Мы будем использовать www.jsonplaceholder.typicode.com который представляет собой поддельный онлайн-API REST для тестирования и создания прототипов.

В index.js который мы создали ранее, давайте определим функцию, которая возвращает список URL-адресов фотографий с учетом идентификатора альбома:

 const axios = require('./axiosConfig'); 
 
 const getPhotosByAlbumId = async (id) => { 
 const result = await axios.request({ 
 method: 'get', 
 url: `/${id}/photos?_limit=3` 
 }); 
 const { data } = result; 
 return data; 
 }; 
 
 module.exports = getPhotosByAlbumId; 

Чтобы попасть в наш API, мы просто используем метод axios.request() нашего экземпляра axios Мы передаем имя метода, который в нашем случае является get и url который мы будем вызывать.

Строка, которую мы передаем в url будет объединена с baseURL из axiosConfig.js .

Теперь давайте настроим Jest-тест для этой функции.

Настройка Jest

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

 $ npm i jest -D 

-D - это ярлык для --save-dev , который указывает NPM сохранить его как зависимость разработки.

Затем мы приступим к созданию файла конфигурации для Jest с именем jest.config.js :

 touch jest.config.js 

Теперь в jest.config.js мы установим каталоги, в которых будут находиться наши тесты:

 module.exports = { 
 testMatch: [ 
 '<rootDir>/**/__tests__/**/?(*.)(spec|test).js', 
 '<rootDir>/**/?(*.)(spec|test).js' 
 ], 
 testEnvironment: 'node', 
 }; 

Значение testMatch - это массив глобальных шаблонов, которые Jest будет использовать для обнаружения тестовых файлов. В нашем случае мы указываем, что любой файл внутри __tests__ или в любом месте нашего проекта, имеющий либо .spec.js либо. test.js следует рассматривать как тестовый файл.

Примечание . В JavaScript тестовые файлы .spec.js заканчиваются на .spec.js. Разработчики используют слово «спецификация» как сокращение от «спецификация» . Подразумевается, что тесты содержат функциональные требования или спецификации для реализуемых функций.

Значение testEnvironment представляет среду, в которой работает Jest, то есть в Node.js или в браузере. Вы можете узнать больше о других допустимых параметрах конфигурации здесь .

Теперь давайте изменим наш package.json так, чтобы он использовал Jest в качестве нашего средства выполнения тестов:

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

Наша установка сделана. Чтобы проверить, что наша конфигурация работает, создайте тестовый файл в корне каталога с именем index.spec.js :

 touch index.spec.js 

Теперь внутри файла напишем тест:

 describe('sum of 2 numbers', () => { 
 it(' 2 + 2 equal 4', () => { 
 expect(2 + 2).toEqual(4) 
 }); 
 }); 

Запустите этот код с помощью следующей команды:

 $ npm test 

У вас должен получиться такой результат:

 PASS ./index.spec.js 
 sum of 2 numbers 
 ✓ 2 + 2 equal 4 (3ms) 
 
 Test Suites: 1 passed, 1 total 
 Tests: 1 passed, 1 total 
 Snapshots: 0 total 
 Time: 0.897s, estimated 1s 
 Ran all test suites. 

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

Имитация HTTP-вызова с помощью Jest

В index.spec.js мы начнем с нуля, удалив старый код и напишем новый скрипт, имитирующий HTTP-вызов:

 const axios = require('./axiosConfig'); 
 const getPhotosByAlbumId = require('./index'); 
 
 jest.mock('./axiosConfig', () => { 
 return { 
 baseURL: 'https://jsonplaceholder.typicode.com/albums', 
 request: jest.fn().mockResolvedValue({ 
 data: [ 
 { 
 albumId: 3, 
 id: 101, 
 title: 'incidunt alias vel enim', 
 url: 'https://via.placeholder.com/600/e743b', 
 thumbnailUrl: 'https://via.placeholder.com/150/e743b' 
 }, 
 { 
 albumId: 3, 
 id: 102, 
 title: 'eaque iste corporis tempora vero distinctio consequuntur nisi nesciunt', 
 url: 'https://via.placeholder.com/600/a393af', 
 thumbnailUrl: 'https://via.placeholder.com/150/a393af' 
 }, 
 { 
 albumId: 3, 
 id: 103, 
 title: 'et eius nisi in ut reprehenderit labore eum', 
 url: 'https://via.placeholder.com/600/35cedf', 
 thumbnailUrl: 'https://via.placeholder.com/150/35cedf' 
 } 
 ] 
 }), 
 } 
 }); 

Здесь мы сначала импортируем наши зависимости, используя синтаксис require Поскольку мы не хотим делать никаких реальных сетевых вызовов, мы вручную создаем макет нашего axiosConfig используя метод jest.mock() . Метод jest.mock() принимает путь к модулю в качестве аргумента и необязательную реализацию модуля в качестве заводского параметра .

Для параметра factory мы указываем, что наш макет, axiosConfig , должен возвращать объект, состоящий из baseURL и request() . baseUrl устанавливается на базовый URL-адрес API. request() - это фиктивная функция, возвращающая массив фотографий.

Функция request() мы здесь определили, заменяет настоящую axios.request() . Когда мы вызываем метод request() , вместо этого вызывается наш фиктивный метод.

Важно отметить jest.fn() . Он возвращает новую фиктивную функцию , и ее реализация определяется в круглых скобках. То, что мы сделали с помощью функции mockResolvedValue() - это новая реализация функции request() .

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

mockResolvedValue() совпадает с mockImplementation(() => Promise.resolve(value)) .

Имея макет, давайте продолжим и напишем тест:

 describe('test getPhotosByAlbumId', () => { 
 afterEach(() => jest.resetAllMocks()); 
 
 it('fetches photos by album id', async () => { 
 const photos = await getPhotosByAlbumId(3); 
 expect(axios.request).toHaveBeenCalled(); 
 expect(axios.request).toHaveBeenCalledWith({ method: 'get', url: '/3/photos?_limit=3' }); 
 expect(photos.length).toEqual(3); 
 expect(photos[0].albumId).toEqual(3) 
 }); 
 }); 

После каждого тестового примера мы гарантируем, что jest.resetAllMocks() вызывается для сброса состояния всех моков.

В нашем тестовом примере мы вызываем getPhotosByAlbumId() с идентификатором 3 в качестве аргумента. Затем мы делаем наши утверждения.

Первое утверждение предполагает, что был axios.request() , а второе утверждение проверяет, что метод был вызван с правильными параметрами. Мы также проверяем, что длина возвращаемого массива равна 3 и что первый объект массива имеет albumId альбома 3 .

Давайте запустим наши новые тесты с помощью:

 npm test 

У нас должен получиться следующий результат:

 PASS ./index.spec.js 
 test getPhotosByAlbumId 
 ✓ fetches photos by album id (7ms) 
 
 Test Suites: 1 passed, 1 total 
 Tests: 1 passed, 1 total 
 Snapshots: 0 total 
 Time: 0.853s, estimated 1s 
 Ran all test suites. 

С этим новым знакомством и опытом давайте проведем быстрое сравнение опыта тестирования с Jest и Sinon, которые также обычно используются для имитации.

Sinon Mocks против Jest Mocks

Sinon.js и Jest по-разному подходят к концепции издевательства. Ниже приведены некоторые из основных отличий, на которые следует обратить внимание:

  • В Jest модули Node.js автоматически имитируются в ваших тестах, когда вы помещаете фиктивные файлы в __mocks__ которая находится рядом с папкой node_modules Например, если у вас есть файл с именем __mock__/fs.js , то каждый раз, когда fs , Jest будет автоматически использовать макеты. С другой стороны, с Sinon.js вы должны вручную sinon.mock() для каждого теста, который в этом нуждается.
  • В Jest мы используем метод jest.fn().mockImplementation() для замены реализации фиктивной функции на заглушенный ответ. Хороший пример этого можно найти в документации Jest здесь . В Sinon.js мы используем метод mock.expects() чтобы справиться с этим.
  • Jest предоставляет большое количество методов для работы с их фиктивным API и, в частности, с модулями. Вы можете просмотреть их все здесь . Sinon.js, с другой стороны, имеет меньше методов для работы с макетами и предоставляет в целом более простой API.
  • Sinon.js имитирует корабль как часть библиотеки Sinon.js, которую можно подключить и использовать в сочетании с другими фреймворками тестирования, такими как Mocha, и библиотеками утверждений, такими как Chai. С другой стороны, Jest имитирует корабль как часть фреймворка Jest, который также поставляется с собственным API утверждений.

Моки Sinon.js часто наиболее полезны, когда вы тестируете небольшое приложение, которое может не требовать всей мощи такого фреймворка, как Jest. Это также полезно, когда у вас уже есть тестовая установка и вам нужно добавить имитацию к нескольким компонентам в вашем приложении.

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

Заключение

В этой статье мы рассмотрели, как с помощью Jest имитировать HTTP-вызов, сделанный с помощью axios . Сначала мы настроили приложение для использования axios качестве нашей библиотеки HTTP-запросов, а затем настроили Jest, чтобы помочь нам с модульным тестированием. Наконец, мы рассмотрели некоторые различия между Sinon.js и Jest mocks и выяснили, когда лучше всего использовать их.

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

Как всегда, код из этого руководства можно найти на GitHub .

comments powered by Disqus