Jest - популярный фреймворк для тестирования кода JavaScript, написанный Facebook. Он поставляется с множеством обычных утилит для тестирования, таких как сопоставители для написания тестовых утверждений и имитирующих функций. Идея имитации функции, которая вызывает API-вызов какой-либо внешней службы, была для меня немного чуждой, пока я не использовал имитацию Jest в работе. В этом посте будет представлен краткий обзор того, как вы можете имитировать функции в ваших тестах, которые обычно вызывают API или выполняют действия CRUD в базе данных.
Ручные макеты Jest
Важной особенностью Jest является то, что он позволяет вам писать ручные макеты, чтобы использовать поддельные данные для ваших собственных модулей в вашем приложении. Для этого вам нужно написать модуль в подкаталоге __mocks__
, непосредственно примыкающем к реальному модулю, и оба файла должны иметь одинаковое имя. Давайте посмотрим на пример.
Скажем, у нас есть приложение Node, которое содержит каталог lib
, а внутри этого каталога находится файл с именем db.js
. В этом файле есть несколько методов, которые выполняют HTTP-запросы к API базы данных. Выглядит это примерно так:
lib / db.js
const request = require('request-promise'); const selectUserById = async userId => { const options = { uri: `https://www.mydbapi.com/users/${userId}`, json: true }; try { const selectResult = await request(options); return { data: selectResult }; } catch (error) { return { error: error.response.body }; } }; const createUser = async userData => { const options = { method: 'POST', uri: 'https://www.mydbapi.com/users', body: userData, json: true }; try { const createResult = await request(options); return { data: createResult }; } catch (error) { return { error: error.response.body }; } }; module.exports = { selectUserById, createUser };
Здесь у нас есть два метода, selectUserById
и createUser
(обычно есть методы для обновления и удаления пользователей, но для того, чтобы этот пример был кратким, мы их исключим). Мы используем библиотеку request-promise
для вызовов API к базе данных. Чтобы смоделировать эту функциональность в наших тестах, мы захотим написать очень похожий модуль в подкаталоге __mocks__
. Это означает, что мы захотим создать еще один db.js
файл, который находится в каталоге lib/__mocks__
. Мы также создадим testData.js
файл в этом каталоге, чтобы мы могли использовать поддельные данные вместо вызова API в наших тестах. Эти два файла будут выглядеть примерно так:
lib / __ издевается над __ / testData.js
const users = [ { id: 1, firstName: 'John', lastName: 'Smith', userName: 'jsmith' }, { id: 2, firstName: 'Jane', lastName: 'Doe', userName: 'jdoe' }, { id: 3, firstName: 'Ben', lastName: 'Morrison', userName: 'benjimorr' } ]; module.exports = { users };
lib / __ издевается над __ / db.js
const _ = require('lodash'); const { users } = require('./testData'); const selectUserById = userId => { return new Promise((resolve, reject) => { const userData = _.filter(users, obj => { return obj.id === userId; }); if (userData) { resolve({ data: userData }); } else { reject({ error: 'User not found.' }); } }); }; const createUser = userData => { return new Promise((resolve, reject) => { if (_.some(users, ['userName', userData.userName])) { reject({ error: 'That username already exists.' }); } else { const newUser = { // NOTE: This is not the best way to create a "unique" id, // I am just using this hack for this example. id: users.length + 1, ...userData }; users.push(newUser); resolve({ data: newUser }); } }); }; module.exports = { selectUserById, createUser };
В нашем имитируемом модуле db.js
мы используем поддельные данные пользователя из файла testData.js
, а также некоторые полезные методы из популярной библиотеки lodash
, чтобы помочь нам найти объекты в массиве поддельных пользователей. Мы также возвращаем обещания из наших имитируемых функций, чтобы имитировать HTTP-запросы, чтобы мы могли использовать async/await
в наших тестах, аналогично тому, как мы это делали бы в нашем производственном коде.
Вы заметите, что наши имитирующие функции имеют те же имена, что и настоящие функции - это важная деталь, и наши фиктивные функции не будут работать, если они будут названы по-другому. А теперь пора написать тесты!
Написание наших тестов
Теперь, когда мы смоделировали наш db.js
модуль, мы можем написать несколько простых тестов, чтобы убедиться, что все работает должным образом, и нам не придется беспокоиться о каких-либо внешних вызовах API.
Примечание. Поскольку в наших тестах нам потребуется модуль
db.js
, использованиеjest.mock('./db.js')
обязательно.
lib / db.test.js
jest.mock('./db'); // This line is important! const db = require('./db'); describe('selectUserById function', () => { it('returns an error for a user that doesn't exist', async () => { const userData = await db.selectUserById('foo'); expect(userData.error).not.toBeNull(); expect(userData.error).toBe('User not found.'); }); it('returns the user data for a user that exists', async () => { const expectedUserData = { id: 1, firstName: 'John', lastName: 'Smith', userName: 'jsmith' }; const userData = await db.selectUserById(1); expect(userData.data).not.toBeNull(); expect(userData.data).toEqual(expectedUserData); }); }); describe('createUser function', () => { it('returns an error if the username exists', async () => { const newUserData = { firstName: 'Jonathan', lastName: 'Smith', userName: 'jsmith' }; const createResult = await db.createUser(newUserData); expect(createResult.error).not.toBeNull(); expect(createResult.error).toBe('That username already exists.'); }); it('returns data for new user when successful', async () => { const newUserData = { firstName: 'Homer', lastName: 'Simpson', userName: 'hsimpson' }; const expectedResult = { id: 4, ...newUserData }; const createResult = await db.createUser(newUserData); expect(createResult.data).not.toBeNull(); expect(createResult.data).toEqual(expectedResult); }); });
Здесь мы написали несколько тестов для наших selectUserById
и createUser
функций. Тесты подтверждают, что мы получаем error
, когда что-то идет не так, и правильный data
, когда все работает успешно.
Примечание. На практике вы захотите создать функцию в вашем
lib/__mocks__/db.js
файле, чтобы вернуть поддельный массивusers
в исходную форму. Вы можете использовать эту функцию в блокеafterEach
, чтобы предотвратить любые странные результаты тестирования, поскольку мы добавляем новые данные в массивusers
в наших тестах.
Заключение
На этом мы завершаем этот учебник о том, как имитировать асинхронные методы при тестировании кода с помощью Jest. Я надеюсь, что вы нашли этот пост полезным и сможете начать использовать эти методы в своих собственных тестах!