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

Jest单元测试express-validator时验证结果为空,该如何正确测试?

如何用Jest测试express-validator的参数校验逻辑

问题描述

我正在用Jest为Express API的控制器路由编写单元测试,控制器里调用了validationResult(req),但运行测试时validationResult返回空值,触发不了预期的参数校验逻辑。

控制器代码

import { Request, Response, NextFunction } from "express";
import { validationResult } from "express-validator";
import TDS from "../DomainLogic/TasmanianDevilService";

async function createTasmanianDevil (
  req: Request,
  res: Response,
) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json(errors.array());
  }

  console.log(errors);

  const result = await TDS.createTasmanianDevilAsync(req.body.attribute);

  if(result === undefined) {
    return res.status(500).json('Error');
  }

  return res.status(200).json(
    result
  );
};

export default { createTasmanianDevil };

验证器代码

import express from "express";
import controller from "../controllers/TasmanianDevilController";
import { check, body, query } from "express-validator"

const router = express.Router();

router.post('/createTasmanianDevil', [
    body('attribute').isInt(),
], controller.createTasmanianDevil);

export = router;

Jest测试代码

import TDC from "../../controllers/TasmanianDevilController";
import TDS from "../../DomainLogic/TasmanianDevilService";
import { getMockReq, getMockRes } from "@jest-mock/express";

jest.mock("../../DomainLogic/TasmanianDevilService");

describe("createTasmanianDevil", () => {
    let req = getMockReq();
    const { res, mockClear } = getMockRes();

    beforeEach(() => {
        TDS.createTasmanianDevilAsync = jest.fn().mockReturnValue(0);
        mockClear();
    });

    test("should validate attribute must be number", async () => {
        req.body.attribute = "not a number"

        await TDC.createTasmanianDevil(req, res);

        expect(res.status).toHaveBeenCalledWith(400);
    });
});

问题原因

直接调用控制器方法时,express-validator的校验中间件并没有被执行——这些校验规则是挂载在路由上的,单独调用控制器函数时,不会自动触发校验逻辑,所以req对象上没有被注入校验错误,validationResult(req)自然返回空。

解决方案

方案1:手动模拟validationResult的返回值

如果只是想测试控制器对校验错误的处理逻辑,可以直接MockvalidationResult函数,让它返回预设的错误结果:

import TDC from "../../controllers/TasmanianDevilController";
import TDS from "../../DomainLogic/TasmanianDevilService";
import { getMockReq, getMockRes } from "@jest-mock/express";
import { validationResult } from "express-validator";

jest.mock("../../DomainLogic/TasmanianDevilService");
// Mock validationResult
jest.mock("express-validator", () => ({
  ...jest.requireActual("express-validator"),
  validationResult: jest.fn(),
}));

describe("createTasmanianDevil", () => {
    let req = getMockReq();
    const { res, mockClear } = getMockRes();

    beforeEach(() => {
        TDS.createTasmanianDevilAsync = jest.fn().mockReturnValue(0);
        mockClear();
        // 重置mock默认返回值
        (validationResult as jest.Mock).mockReturnValue({
          isEmpty: () => true,
          array: () => [],
        });
    });

    test("should return 400 when validation fails", async () => {
        // 让validationResult返回错误
        (validationResult as jest.Mock).mockReturnValue({
          isEmpty: () => false,
          array: () => [{ msg: "attribute must be an integer" }],
        });

        await TDC.createTasmanianDevil(req, res);

        expect(res.status).toHaveBeenCalledWith(400);
        expect(res.json).toHaveBeenCalledWith([{ msg: "attribute must be an integer" }]);
    });

    test("should proceed when validation passes", async () => {
        req.body.attribute = 123;

        await TDC.createTasmanianDevil(req, res);

        expect(TDS.createTasmanianDevilAsync).toHaveBeenCalledWith(123);
        expect(res.status).toHaveBeenCalledWith(200);
    });
});

方案2:手动执行校验链,模拟真实校验过程

如果想真实触发express-validator的校验逻辑,而非Mock结果,可以手动获取路由上的校验中间件,对req执行校验:

import TDC from "../../controllers/TasmanianDevilController";
import TDS from "../../DomainLogic/TasmanianDevilService";
import { getMockReq, getMockRes } from "@jest-mock/express";
import router from "../../routes/TasmanianDevilRouter"; // 引入路由文件
import { validationResult } from "express-validator";

jest.mock("../../DomainLogic/TasmanianDevilService");

describe("createTasmanianDevil", () => {
    let req = getMockReq();
    const { res, mockClear } = getMockRes();

    beforeEach(() => {
        TDS.createTasmanianDevilAsync = jest.fn().mockReturnValue(0);
        mockClear();
    });

    test("should validate attribute must be number", async () => {
        req.body.attribute = "not a number";
        
        // 获取路由上的校验中间件数组
        const routeLayer = router.stack.find(
            layer => layer.path === "/createTasmanianDevil" && layer.method === "post"
        );
        const validationMiddlewares = (routeLayer.handle as any);

        // 依次执行所有校验中间件
        await Promise.all(validationMiddlewares.map((mw: any) => mw(req, res, jest.fn())));

        // 此时req已携带校验结果,调用控制器
        await TDC.createTasmanianDevil(req, res);

        expect(res.status).toHaveBeenCalledWith(400);
    });
});

方案3:用Supertest测试完整路由

如果要测试整个请求流程(包括路由匹配、中间件执行、控制器处理),推荐用Supertest,它能模拟HTTP请求,自动触发所有路由中间件:

首先安装依赖:

npm install supertest --save-dev

编写测试代码:

import request from "supertest";
import express from "express";
import router from "../routes/TasmanianDevilRouter";
import TDS from "../DomainLogic/TasmanianDevilService";

jest.mock("../DomainLogic/TasmanianDevilService");

// 初始化Express应用
const app = express();
app.use(express.json());
app.use(router);

describe("POST /createTasmanianDevil", () => {
    beforeEach(() => {
        TDS.createTasmanianDevilAsync = jest.fn().mockReturnValue(0);
    });

    test("should return 400 when attribute is not an integer", async () => {
        const response = await request(app)
            .post("/createTasmanianDevil")
            .send({ attribute: "not a number" });

        expect(response.status).toBe(400);
        expect(response.body).toEqual(
            expect.arrayContaining([expect.objectContaining({ msg: expect.stringContaining("must be an integer") })])
        );
    });

    test("should return 200 when attribute is valid", async () => {
        const response = await request(app)
            .post("/createTasmanianDevil")
            .send({ attribute: 123 });

        expect(response.status).toBe(200);
        expect(TDS.createTasmanianDevilAsync).toHaveBeenCalledWith(123);
    });
});

总结

  • 若仅测试控制器对错误的处理逻辑,MockvalidationResult的方式最简单高效;
  • 若想验证校验规则本身是否生效,手动执行校验链或用Supertest测试完整路由更准确;
  • Supertest适合集成测试场景,能覆盖从请求到响应的全流程,推荐用于验证路由与校验的组合逻辑。

内容的提问来源于stack exchange,提问作者Hactavius

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 20:55:22