#javascript #typescript #testing #jestjs #integration-testing
#javascript #typescript #тестирование #jestjs #интеграция-тестирование
Вопрос:
У меня есть интеграционный тест, в котором я делаю фактические вызовы DB для базы данных MongoDB. Но для того, чтобы проверить, истек срок действия транзакции или нет, мне нужно издеваться над DB для этого конкретного теста. У меня есть много причин сделать фактический вызов DB, я упоминаю состояние только ради этого примера.
У Jest есть jest.doMock
функция, но это полезно только тогда, когда я хотел импортировать функцию в тест, но в моем случае это функция DB, которую я хотел смоделировать для этого конкретного теста, когда вызывается внутри промежуточного программного обеспечения express.
Есть еще один вариант издевательства над всем ../db
модулем, но это сильно усложнит тесты в моем реальном проекте. Для меня было бы очень легко, если бы я мог издеваться над вызовом DB для определенного теста, а для остальных всех тестов он должен выполнять реальные вызовы DB.
Есть ли способ сделать это в шутку?
// a.ts
import express from "express"
import db from "../db";
const app = express()
app.get("/api/deduct-balance/:txn_id", (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
});
// a.test.ts
import db from "../db";
describe("mixed tests", () => {
test("should make REAL db calls", async () => {
await axios.get("/api/deduct-balance/123")
const txn = await db.findById("123");
expect(txn.state).toBe("DEDUCTED");
});
test("should use MOCKED value", async () => {
// need a way to mock the DB call so that I can return an expired transaction
// when I hit the API
const { data } = await axios.get("/api/deduct-balance/123")
expect(data).toBe({
error: {
message: "txn expired"
}
});
});
})
Комментарии:
1. Такого рода тесты лучше не выполнять как интеграционные тесты. Если вы хотите протестировать поведение обработчика запроса, вы должны издеваться над всеми зависимостями, чтобы создать предсказуемый и повторяемый тест. Модуль
db
должен быть полным макетом иfindById
должен возвращать фиктивную транзакцию и так далее…2. @Bart тогда какой тип тестов я должен выполнять в качестве интеграционных тестов? Интересно узнать, что вы думаете об этом.
3. Я оставил ответ об общей сути. К сожалению, мне нужно идти, но я могу расширить свой ответ на более позднем этапе, чтобы сделать его более понятным.
Ответ №1:
Интеграционные тесты излишни для этого сценария. Достаточно простых модульных тестов. Они быстро выполняются, тестируют только одну вещь, и у вас их должно быть много.
Поскольку вы определяете обработчик как анонимную функцию, по умолчанию сложно выполнить модульное тестирование. Итак, первое действие — упростить тестирование, извлекая его.
// deduct-balance-handlers.ts
export const deductBalanceByTransaction = async (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
}
Это также сделает конфигурацию приложения более чистой.
// a.ts
import express from "express"
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
const app = express()
app.get("/api/deduct-balance/:txn_id", deductBalanceByTransaction);
Теперь легко повторно использовать обработчик в вашем тесте, не полагаясь на веб-фреймворк или базу данных.
// a.test.ts
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
jest.mock('../db');
describe("deduct-balance", () => {
test("Expired transaction should respond with 401 status", async () => {
const response = mockResponse();
deductBalanceByTransaction(request, response);
expect(response.status).toBe(401);
});
})
Для простоты я оставил часть создания макетного ответа и издевательства над модулем из кода. Больше можно узнать о издевательствах здесь: https://jestjs.io/docs/en/manual-mocks
Комментарии:
1. Я действительно ценю ответ @Bart, но я намеренно сохранил фрагменты кода примера небольшими, чтобы упростить объяснение. В реальном проекте это намного сложнее, и именно поэтому мне нужен интеграционный тест. Подход, который вы упомянули выше, я использую во многих своих тестах, но моя главная проблема здесь заключается в том, могу ли я издеваться над модулями, которые неявно зависят только в одном тесте. Если вы можете сказать мне, возможно ли это в шутку или нет, тогда это было бы очень полезно. 🙂
2. Одна вещь, которую я бы рассмотрел в первую очередь, это подумать об архитектуре кода. Мешает ли это тестированию? Если это так, у вас есть указание на то, что в дизайне кода что-то не так. В случае вашего примера у обработчика много обязанностей, таких как общение с базой данных и принятие решения о крайних случаях. Обычно обработчики запросов выполняют только одну вещь, обрабатывая ввод (запрос) и вывод (ответ) объектов уровня сервиса. Он не должен делать ничего другого, поскольку это транспортный уровень HTTP. Если дизайн прост, у вас будет меньше зависимостей и вы будете меньше иметь дело с макетами.
3. Решение, которое вы хотите для своего теста, не является хорошим решением. Издевательство над модулем может сработать, если вы запускаете тесты в разных наборах / файлах, но я бы не рекомендовал это, потому что это сбивает с толку. Вы, скорее всего, слишком усложните свои тесты, потому что код не может быть легко протестирован в его текущем дизайне.
4. Хорошо, предполагая, что мой дизайн кода не очень хорош, и я хотел его изменить. Есть ли у вас определенные библиотеки с открытым исходным кодом в качестве примера, предпочтительно серверных проектов? Или чей-то другой, который я могу искать для определенного подхода.
5. Это не то, чему вы можете научиться, глядя на другие проекты. Вы пропустите намерение о том, почему он разработан таким, какой он есть. Это больше о разработке программного обеспечения и разбиении вещей на разумные модули. Сведите обязанности и зависимости к минимуму и переосмысливайте свои проекты снова и снова.