Как тестировать экспресс-API с помощью Jest
Использование Express для создания вашего API и Jest для его тестирования — выигрышная комбинация для повышения надежности вашего приложения.
Тестирование, хотя оно и может занять много времени, является важным шагом в цикле разработки любого приложения. Это гарантирует, что вы обнаружите ошибки и проблемы на ранней стадии, прежде чем вы отправите код в производство.
Вы можете использовать Jest для тестирования API Express Rest. Создав простой API CRUD, узнайте, как писать тесты для каждой конечной точки.
Что такое шутка?
Существует множество библиотек тестирования JavaScript, из которых вы можете выбирать, но начать проще всего с Jest. Это библиотека тестирования, разработанная Facebook и в основном используемая для тестирования проектов React. Однако вы также можете использовать его для тестирования Node и других проектов на основе JavaScript. Он был разработан на базе другого инструмента тестирования Jasmine и поставляется в комплекте с собственной библиотекой утверждений.
Хотя для написания тестов в Jest вам не понадобится библиотека утверждений, вам понадобится инструмент для выполнения HTTP-запросов. В этой статье используется SuperTest.
Что такое супертест?
SuperTest — это библиотека тестирования Node для HTTP-вызовов. Он расширяет библиотеку тестирования суперагентов и позволяет выполнять такие запросы, как GET, POST, PUT и DELETE.
SuperTest предоставляет объект запроса, который можно использовать для выполнения HTTP-запросов.
const request = require("supertest")
request("https://icanhazdadjoke.com")
.get('/slack')
.end(function(err, res) {
if (err) throw err;
console.log(res.body.attachments);
});
Здесь вы передаете базовый URL-адрес API объекту запроса, а затем связываете метод HTTP с остальной частью URL-адреса. Метод end() вызывает сервер API, а функция обратного вызова обрабатывает его ответ.
Получив ответ от API, вы можете использовать Jest для его проверки.
Создайте экспресс-API
Чтобы протестировать собственные конечные точки API, вам необходимо сначала создать REST API. API, который вы создадите, довольно прост. Он вставляет, извлекает, обновляет и удаляет элементы из массива.
Начните с создания нового каталога под названием node-jest и инициализации npm.
mkdir node-jest
npm init -y
Затем создайте новый файл с именем index.js и создайте сервер Express.
const express = require("express")
const app = express()
app.listen(3000, () => console.log("Listening at port 3000"))
Проверьте конечную точку GET /todos
Первая конечная точка, которую вы создадите, — это конечная точка GET/todos. Он возвращает все элементы массива. В index.js добавьте следующее.
const todos = [
];
// Get all todos
app.get("/todos", (req, res) => {
return res.status(200).json({
data: todos,
error: null,
});
});
Обратите внимание, что ответ имеет код состояния 200 и объект JSON, содержащий элемент списка дел в массиве с именем data и сообщение об ошибке. Это то, что вы протестируете с помощью Jest.
Теперь установите Jest и SuperTest:
npm install jest supertest
Затем добавьте тестовый скрипт в package.json следующим образом:
{
"scripts": {
"test": "jest"
}
}
Прежде чем приступить к написанию собственных тестов, вы должны понять, как написать базовый тест в Jest.
Рассмотрим следующую функцию:
function sum(a, b) {
return a + b;
}
module.exports = sum;
В тестовом файле вам необходимо:
- Импортируйте функцию.
- Опишите, что должен делать тест.
- Вызовите функцию.
- Подтвердите ожидаемый ответ фактическим ответом функции.
const { sum } = require("./sum")
describe("Sum of two items", async() => {
test("It should return 4", () => {
expect(sum(2,2)).toBe(4)
})
})
Ключевое слово describe указывает группу тестов, а оператор test указывает конкретный тест. Если значение, возвращаемое функцией, соответствует значению, переданному в toBe, тест считается пройденным.
При тестировании конечных точек API вы не будете вызывать функцию, а отправите запрос с помощью SuperTest или другой клиентской библиотеки HTTP.
Вернувшись к конечной точке GET, создайте новый файл с именем api.test.js. Здесь вы будете писать все тесты конечных точек. Присвоение тестовому файлу имени с инфиксом .test гарантирует, что Jest распознает его как тестовый файл.
В api.test.js импортируйте супертест и установите базовый URL-адрес следующим образом:
const request = require("supertest")
const baseURL = "http://localhost:3000"
Далее создайте первый тест в блоке описания:
describe("GET /todos", () => {
const newTodo = {
id: crypto.randomUUID(),
item: "Drink water",
completed: false,
}
beforeAll(async () => {
// set up the todo
await request(baseURL).post("/todo").send(newTodo);
})
afterAll(async () => {
await request(baseURL).delete(`/todo/${newTodo.id}`)
})
it("should return 200", async () => {
const response = await request(baseURL).get("/todos");
expect(response.statusCode).toBe(200);
expect(response.body.error).toBe(null);
});
it("should return todos", async () => {
const response = await request(baseURL).get("/todos");
expect(response.body.data.length >= 1).toBe(true);
});
});
Перед запуском тестов вам необходимо определить функции настройки и удаления. Эти функции будут заполнять массив задач элементом перед тестом и удалять фиктивные данные после каждого теста.
Код, который выполняется перед всеми тестами, находится в функции beforeAll(). Код, который запускается после всех тестов, находится в функции afterAll().
В этом примере вы просто нажимаете конечные точки POST и DELETE для каждого из них. В реальном приложении вы, вероятно, подключитесь к фиктивной базе данных, содержащей тестовые данные.
В этом тесте вы сначала сделали запрос к конечной точке GET/todos и сравнили отправленный ответ с ожидаемыми результатами. Этот набор тестов будет пройден, если ответ имеет код состояния HTTP 200, данные не пусты и сообщение об ошибке имеет нулевое значение.
Проверьте конечную точку POST /todo
В index.js создайте конечную точку POST/todo:
app.post("/todo", (req, res) => {
try {
const { id, item, completed } = req.body;
const newTodo = {
id,
item,
completed,
};
todos.push(newTodo);
return res.status(201).json({
data: todos,
error: null,
});
} catch (error) {
return res.status(500).json({
data: null,
error: error,
});
}
});
В этом тесте вам нужно будет отправить сведения о задаче в теле запроса с помощью метода send().
request(baseURL).post("/todo").send(newTodo)
Запрос POST /todo должен возвращать код состояния 201 и массив задач с добавленным в конце новым элементом. Вот как может выглядеть тест:
describe("POST /todo", () => {
const newTodo = {
// todo
}
afterAll(async () => {
await request(baseURL).delete(`/todo/${newTodo.id}`)
})
it("should add an item to todos array", async () => {
const response = await request(baseURL).post("/todo").send(newTodo);
const lastItem = response.body.data[response.body.data.length-1]
expect(response.statusCode).toBe(201);
expect(lastItem.item).toBe(newTodo["item"]);
expect(lastItem.completed).toBe(newTodo["completed"]);
});
});
Здесь вы передаете данные задачи методу send() в качестве аргумента. Ответ должен иметь код состояния 201, а также содержать все элементы задач в объекте данных. Чтобы проверить, действительно ли задача была создана, проверьте, соответствует ли последняя запись в возвращенных задачах той, которую вы отправили в запросе.
Конечная точка PUT /todos/:id должна возвращать обновленный элемент:
app.put("/todos/:id", (req, res) => {
try {
const id = req.params.id
const todo = todos.find((todo) => todo.id == id);
if(!todo) {
throw new Error("Todo not found")
}
todo.completed = req.body.completed;
return res.status(201).json({
data: todo,
error: null,
});
} catch (error) {
return res.status(500).json({
data: null,
error: error,
});
}
});
Проверьте ответ следующим образом:
describe("Update one todo", () => {
const newTodo = {
// todo
}
beforeAll(async () => {
await request(baseURL).post("/todo").send(newTodo);
})
afterAll(async () => {
await request(baseURL).delete(`/todo/${newTodo.id}`)
})
it("should update item if it exists", async () => {
const response = await request(baseURL).put(`/todos/${newTodo.id}`).send({
completed: true,
});
expect(response.statusCode).toBe(201);
expect(response.body.data.completed).toBe(true);
});
});
Завершенное значение в теле ответа должно быть истинным. Не забудьте включить в URL-адрес идентификатор элемента, который вы хотите обновить.
Проверьте конечную точку DELETE /todos/:id
В index.js создайте конечную точку DELETE. Он должен вернуть данные задачи без удаленного элемента.
app.delete("/todos/:id", (req, res) => {
try {
const id = req.params.id
const todo = todos[0]
if(todo) {
todos.splice(id, 1)
}
return res.status(200).json({
data: todos,
error: null,
});
} catch (error) {
return res.status(500).json({
data: null,
error: error,
});
}
});
Чтобы протестировать конечную точку, вы можете проверить, существует ли удаленный элемент в возвращаемых данных:
describe("Delete one todo", () => {
const newTodo = {
// todo
}
beforeAll(async () => {
await request(baseURL).post("/todo").send(newTodo);
})
it("should delete one item", async () => {
const response = await request(baseURL).delete(`/todos/${newTodo.id}`);
const todos = response.body.data
const exists = todos.find(todo => {
newTodo.id == todoId
})
expect(exists).toBe(undefined)
});
});
Данные, возвращаемые из конечной точки DELETE, не должны содержать удаленный элемент. Поскольку возвращаемые элементы находятся в массиве, вы можете использовать Array[id], чтобы проверить, правильно ли API удалил элемент. Результат должен быть ложным.
Создание REST API
В этой статье вы узнали, как протестировать Express Rest API с помощью Jest API. Вы написали тесты для HTTP-запросов GET, PUT, POST и DELETE и увидели, как отправлять данные в конечную точку в URL-адресе и запросе. Вы сможете применить эти знания при тестировании собственного Rest API.