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

登录控制器功能正常但数据库异常场景单元测试失败

登录控制器数据库访问失败场景单元测试失败排查

负责的登录控制器功能正常,但验证数据库访问失败时返回500状态码的单元测试始终失败。已用sinon包mock mongoose的findOne方法模拟数据库失败场景,但测试报错。

测试代码:

describe("Auth Controller - Login", function () {
    it("should throw an error with code 500 if accessing the database fails", function (done) {
        sinon.stub(User, "findOne");
        User.findOne.throws();

        const req = {
            body: {
                email: "test@test.com",
                password: "test123",
            },
        };

        authController
            .postLogin(req, {}, () => {})
            .then((result) => {
                expect(result).to.be.an("error");
                expect(result).to.have.property("httpStatusCode", 500);
                done();
            });

        User.findOne.restore();
    });
});

控制器代码:

exports.postLogin = (req, res, next) => {
    const email = req.body.email;
    const password = req.body.password;

    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(422).json({ error: errors.array()[0].msg });
    }

    User.findOne({ email: email })
        .then((user) => {
            if (!user) {
                return res.status(401).json({ error: "User does not exist!" });
            }
            bcrypt
                .compare(password, user.password)
                .then((doMatch) => {
                    if (doMatch) {
                        const token = jwt.sign(
                            { email: email, userId: user._id },
                            process.env.SESSION_SECRET,
                            {
                                expiresIn: "3d",
                            }
                        );
                        return res.status(200).json({
                            token: token,
                            userId: user._id.toString(),
                            email: email,
                        });
                    }
                    return res.status(401).json({ error: "Invalid Password!" });
                })
                .catch((err) => {
                    throw new Error(err);
                });
        })
        .catch((err) => {
            const error = new Error(err);
            error.httpStatusCode = 500;
            next(error);
            return error;
        });
};

测试报错信息:

1 failing

  1) Auth Controller - Login
       should throw an error with code 500 if accessing the database fails:
     Error
   Error
      at Object.fake.exceptionCreator (node_modules/sinon/lib/sinon/default-behaviors.js:24:20)
      at Object.invoke (node_modules/sinon/lib/sinon/behavior.js:165:35)
      at Function.functionStub (node_modules/sinon/lib/sinon/stub.js:42:43)
      at Function.invoke (node_modules/sinon/lib/sinon/proxy-invoke.js:50:47)
      at Function.findOne (node_modules/sinon/lib/sinon/proxy.js:285:26)
      at Object.exports.postLogin (controllers/auth.js:68:7)
      at Context.<anonymous> (test/auth-controller.js:19:5)
      at processImmediate (internal/timers.js:464:21)

问题原因及修复方案

1. 控制器未返回完整Promise链

postLogin函数没有返回User.findOne()启动的Promise链,导致测试中调用.then()时因undefined无该方法报错,同时catch块返回的error也无法被测试捕获。

修改控制器,返回完整Promise链:

exports.postLogin = (req, res, next) => {
    const email = req.body.email;
    const password = req.body.password;

    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(422).json({ error: errors.array()[0].msg });
    }

    // 返回Promise链,让测试可捕获后续结果
    return User.findOne({ email: email })
        .then((user) => {
            if (!user) {
                return res.status(401).json({ error: "User does not exist!" });
            }
            // 返回bcrypt的Promise链,避免嵌套导致的未捕获错误
            return bcrypt
                .compare(password, user.password)
                .then((doMatch) => {
                    if (doMatch) {
                        const token = jwt.sign(
                            { email: email, userId: user._id },
                            process.env.SESSION_SECRET,
                            {
                                expiresIn: "3d",
                            }
                        );
                        return res.status(200).json({
                            token: token,
                            userId: user._id.toString(),
                            email: email,
                        });
                    }
                    return res.status(401).json({ error: "Invalid Password!" });
                })
                .catch((err) => {
                    const error = new Error(err);
                    error.httpStatusCode = 500;
                    throw error; // 抛出错误让外层catch统一处理
                });
        })
        .catch((err) => {
            const error = new Error(err);
            error.httpStatusCode = 500;
            next(error);
            return error; // 返回错误给测试用例
        });
};

2. Sinon Stub恢复时机错误

测试中User.findOne.restore()放在.then()之前,会导致stub在postLogin调用前就被恢复,findOne不会抛出预期错误。需将恢复操作放在测试完成后的回调中:

修改测试代码:

describe("Auth Controller - Login", function () {
    it("should throw an error with code 500 if accessing the database fails", function (done) {
        const findOneStub = sinon.stub(User, "findOne");
        findOneStub.throws(new Error("Database error"));

        // Mock validationResult,避免验证逻辑干扰数据库失败场景测试
        const validationStub = sinon.stub(require('express-validator'), 'validationResult')
            .returns({ isEmpty: () => true });

        const req = {
            body: {
                email: "test@test.com",
                password: "test123",
            },
        };

        authController
            .postLogin(req, {}, (err) => {
                // 同时通过next回调验证错误传递
                expect(err).to.have.property("httpStatusCode", 500);
            })
            .then((result) => {
                expect(result).to.be.an("error");
                expect(result).to.have.property("httpStatusCode", 500);
                // 测试完成后恢复stub
                findOneStub.restore();
                validationStub.restore();
                done();
            })
            .catch((err) => {
                // 捕获测试异常,避免done未调用
                findOneStub.restore();
                validationStub.restore();
                done(err);
            });
    });
});

3. 未Mock validationResult

测试中未mockvalidationResult(req),会导致errors.isEmpty()可能返回false,提前返回422状态码,无法进入数据库查询逻辑。需mock该方法返回空错误结果。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 06:01:04