Node Express Todo应用Jest测试失败排查:response.body未定义
Node.js + Express Todo应用测试失败问题修复
我正在尝试让Node.js + Express应用的Jest测试用例通过,但始终有两个测试用例失败:
Update Task › Update a task 1Get task by ID › the taskName should be updated
这是一个简易Todo应用的CRUD功能,测试用例基于Jest编写,失败原因均为获取到response.body undefined。任务要求基于空Express服务器构建可运行的Todo列表程序,使用index.js中的taskList存储任务对象,服务器需实现指定路由规则,可使用AJV验证请求体。我尝试直接返回完整对象,但始终无法获取response.body.taskName,相关项目代码如下:
index.test.js
const { expect } = require("@jest/globals"); const request = require("supertest"); const fs = require("fs"); const { validate: validateUUID } = require("uuid"); class test_class { constructor() { this.test_result = {}; this.test_count = 0; } update_report(stdin, stdout, out, display_name = "") { let key = "test" + String(this.test_count); let test_case = { in: `${stdin}`, expected: `${stdout}`, actual: `${out}`, passed: out === stdout, display_name: display_name, }; this.test_result[key] = test_case; this.test_count++; } writeFile() { var testObject = this.test_result; fs.writeFileSync( "./test_report.json", JSON.stringify(testObject, null, 4), (err) => { if (err) { console.error(err); return; } } ); } } function isValidUUID(inputString) { return validateUUID(inputString); } const task = { taskName: "Test Task", status: "In progress", created: "2022-02-14T11:07:05", }; const update_task = { taskName: "new Task", }; const test_client = new test_class(); let task_id; const app = require("./index"); describe("Get request to the root", () => { test("the response should be ok", async () => { const response = await request(app).get("/"); test_client.update_report("/", "Hello World!", response.text); test_client.writeFile(); expect(response.text).toBe("Hello World!"); }); }); describe("Create new task", () => { test("the returned task id should be a valid uuid", async () => { const response = await request(app).post("/task").send(task); test_client.update_report("/task", 200, response.statusCode); test_client.writeFile(); task_id = response.text; test_client.update_report( "No input for this test", true, validateUUID(task_id), `Check if the returned ID is a valid UUID but recived ${task_id}` ); test_client.writeFile(); expect(validateUUID(task_id)).toBe(true); expect(response.statusCode).toBe(200); }); }); describe("Get task by ID", () => { console.log("Get task by ID 0"); test("the returned task should contain the right taskName", async () => { const response = await request(app).get(`/task/${task_id}`); test_client.update_report(`/task/${task_id}`, 200, response.statusCode); test_client.update_report(`/task/${task_id}`, task.taskName, response.text); test_client.writeFile(); expect(response.text).toEqual(task.taskName); }); }); describe("Get All Tasks", () => { test("should return an array with the length of ", async () => { const response = await request(app).get(`/tasks`); test_client.update_report(`/tasks`, 200, response.statusCode); test_client.update_report(`/task/${task_id}`, 1, response.body.length); test_client.writeFile(); expect(1).toEqual(response.body.length); }); }); describe("Update Task", () => { test("Update a task 1", async () => { const response = await request(app) .patch(`/task/${task_id}`) .send(update_task); test_client.update_report(`/task/${task_id}`, 204, response.statusCode); test_client.writeFile(); expect(response.body.taskName).toEqual(task.taskName); }); }); describe("Get task by ID", () => { test("the taskName should be updated", async () => { const response = await request(app).get(`/task/${task_id}`); test_client.update_report(`/task/${task_id}`, 200, response.statusCode); test_client.update_report( `/task/${task_id}`, update_task.taskName, response.body.taskName ); test_client.writeFile(); expect(response.body.taskName).toEqual(update_task.taskName); }); }); describe("Get task by ID", () => { test("the taskName should be updated", async () => { const response = await request(app).delete(`/task/${task_id}`); test_client.update_report(`/task/${task_id}`, 204, response.statusCode); test_client.writeFile(); expect(response.statusCode).toEqual(204); }); });
index.js
const express = require("express"); const bodyParser = require("body-parser"); const { v4: uuidv4, validate: validateUUID } = require('uuid'); const Ajv = require("ajv"); const taskSchema = require("./models/schema"); const app = express(); app.use(bodyParser.json()); let taskList = []; const ajv = new Ajv(); const validateTask = ajv.compile(taskSchema); app.get('/', (req, res) => { res.send('Hello World!'); }); app.post('/task', (req, res) => { const task = req.body; const valid = validateTask(task); if (!valid) { return res.status(400).send(validateTask.errors); } const taskId = uuidv4(); taskList.push({ [taskId]: { ...task, uuid: taskId } }); res.status(200).send(taskId); }); app.get('/tasks', (req, res) => { res.send(taskList); }); app.get('/task/:id', (req, res) => { const taskId = req.params.id; const index = taskList.findIndex(task => task[taskId]); if (index !== -1) { res.status(200).send(taskList[index][taskId].taskName); } else { res.sendStatus(204); } }); app.patch('/task/:id', (req, res) => { const taskId = req.params.id; const update = req.body.taskName; const valid = validateTask(update); if (!valid) { return res.status(400).send(validateTask.errors); } const index = taskList.findIndex(task => task[taskId]) if (index !== -1) { taskList[index][taskId] = { ...taskList[index][taskId], taskName: update }; res.status(200).send(taskList[index][taskId]); } else { res.sendStatus(404); } }); app.delete('/task/:id', (req, res) => { const taskId = req.params.id; const index = taskList.findIndex(task => task[taskId]); if (index !== -1) { taskList.splice(index, 1); res.sendStatus(204).send(taskList[index]); } else { res.sendStatus(204); } }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); module.exports = app;
schema.js
const task = { type: "object", properties: { taskName: {type: "string"}, status: {type: "string"}, created: {type: "string"} }, required: ["taskName", "status", "created"], additionalProperties: false }
package.json
{ "name": "express-js-1", "version": "1.0.0", "description": "", "main": "index.js", "dependencies": { "ajv": "^8.12.0", "babel-cli": "^6.26.0", "babel-preset-env": "^1.7.0", "express": "^4.17.1", "jest": "^27.0.6", "nodemon": "^3.1.0", "superagent": "^6.1.0", "supertest": "^6.1.3", "uuid": "^9.0.0" }, "devDependencies": { "@babel/cli": "^7.22.10", "@babel/preset-env": "^7.22.10" }, "scripts": { "test": "jest --watchAll=false --forceExit", "start": "nodemon index.js" }, "author": "", "license": "ISC" }
问题分析与修复方案
核心问题
- 路由返回格式错误:
GET /task/:id仅返回字符串类型的taskName,而非JSON对象,导致测试用例中response.body为undefined;PATCH路由的验证逻辑和返回状态码与测试预期不匹配。 - 数据存储结构低效:使用数组存储带键的对象,查找和更新操作冗余。
- Schema未正确导出:
schema.js未导出验证规则,导致AJV验证失效。 - HTTP状态码使用不规范:未找到资源时返回204(无内容)不符合REST规范,应返回404;204状态码不允许附带响应体。
修复后的代码
1. 修复index.js
const express = require("express"); const bodyParser = require("body-parser"); const { v4: uuidv4, validate: validateUUID } = require('uuid'); const Ajv = require("ajv"); const taskSchema = require("./models/schema"); const app = express(); app.use(bodyParser.json()); // 改为对象存储,提升查找效率 let taskList = {}; const ajv = new Ajv(); const validateTask = ajv.compile(taskSchema); // 单独定义更新请求的Schema(允许部分字段更新) const updateTaskSchema = { type: "object", properties: { taskName: { type: "string" }, status: { type: "string" } }, minProperties: 1, additionalProperties: false }; const validateUpdateTask = ajv.compile(updateTaskSchema); app.get('/', (req, res) => { res.send('Hello World!'); }); app.post('/task', (req, res) => { const task = req.body; const valid = validateTask(task); if (!valid) { return res.status(400).json(validateTask.errors); } const taskId = uuidv4(); taskList[taskId] = { ...task, uuid: taskId }; res.status(200).send(taskId); }); app.get('/tasks', (req, res) => { // 返回任务数组而非原始对象 res.json(Object.values(taskList)); }); app.get('/task/:id', (req, res) => { const taskId = req.params.id; const task = taskList[taskId]; if (task) { res.status(200).json(task); // 返回完整JSON对象 } else { res.sendStatus(404); // 未找到资源返回404 } }); app.patch('/task/:id', (req, res) => { const taskId = req.params.id; const updateData = req.body; const valid = validateUpdateTask(updateData); if (!valid) { return res.status(400).json(validateUpdateTask.errors); } const task = taskList[taskId]; if (task) { taskList[taskId] = { ...task, ...updateData }; res.sendStatus(204); // 符合测试预期返回204 } else { res.sendStatus(404); } }); app.delete('/task/:id', (req, res) => { const taskId = req.params.id; if (taskList[taskId]) { delete taskList[taskId]; res.sendStatus(204); } else { res.sendStatus(404); } }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); module.exports = app;
2. 修复schema.js(导出验证规则)
module.exports = { type: "object", properties: { taskName: { type: "string" }, status: { type: "string" }, created: { type: "string" } }, required: ["taskName", "status", "created"], additionalProperties: false };
3. 修复测试用例中的错误(Update Task测试)
describe("Update Task", () => { test("Update a task 1", async () => { const response = await request(app) .patch(`/task/${task_id}`) .send(update_task); test_client.update_report(`/task/${task_id}`, 204, response.statusCode); test_client.writeFile(); expect(response.statusCode).toBe(204); // 204无响应体,断言状态码即可 }); });
内容的提问来源于stack exchange,提问作者WAQAR Hussain
相关产品推荐
相关产品推荐

