You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node Express Todo应用Jest测试失败排查:response.body未定义

Node.js + Express Todo应用测试失败问题修复

我正在尝试让Node.js + Express应用的Jest测试用例通过,但始终有两个测试用例失败:

  1. Update Task › Update a task 1
  2. Get 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"
}

问题分析与修复方案

核心问题

  1. 路由返回格式错误:GET /task/:id仅返回字符串类型的taskName,而非JSON对象,导致测试用例中response.body为undefined;PATCH路由的验证逻辑和返回状态码与测试预期不匹配。
  2. 数据存储结构低效:使用数组存储带键的对象,查找和更新操作冗余。
  3. Schema未正确导出:schema.js未导出验证规则,导致AJV验证失效。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 21:07:33