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

Mocha/Chai/Chai-Http测试Express应用时单元测试随机无规律失败

Express + Mocha接口测试随机失败问题排查

问题背景

使用Mocha/Chai/Chai-Http工具链对Express应用开展接口测试时,大部分场景下测试用例可正常通过,但会随机出现部分用例失败的情况,无固定复现规律。
涉及的测试代码如下:

describe("projectTest :>> login", () => {
  // Create a user in order to have the  access token  to execute authenticated API
  before(function (done) {
    let person = new User({
      email: "dev.test@test.com",
      password: "<hashed password>",
      firstName: "dev",
      lastName: "test",
      jobPosition: "Web Developer",
    });
    person.save();

    done();
  });

  /*
   * Test the /POST login route
   */
  describe(" projectTest :>> /POST login ", () => {
    it("it should success if credential (person) is valid ", done => {
      chai
        .request(server)
        .post("/api/projectTest/account/login")
        .send({
          email: "dev.test@test.com",
          password: "<password>",
        })
        .end(function (error, response) {
          if (error) done(error);

          response.should.have.status(200);
          response.body.should.have.property("token");
          done();
        });
    });
  });

  after(function () {
    User.deleteMany({}, function () {});
  });
});

测试执行命令:

cross-env NODE_ENV=test PROJECT_NAME=projectTest mocha --timeout 20000 ./routes/projectTest/**/*.spec.js --exit

运行时抛出的错误信息:

Uncaught AssertionError: expected { Object (_events, _eventsCount, ...) } to have status code 200 but got 400

当前项目相关依赖版本:

  • chai 4.2.0
  • mocha 10.0.0
  • chai-http 4.3.0

问题根本诱因

  • 核心问题是测试生命周期钩子的异步逻辑未做等待:before钩子中person.save()是Mongoose的异步数据库写入操作,但代码在调用save后立刻执行了done(),没有等待数据真正落库。当数据库写入耗时比测试用例启动耗时长时,登录接口执行时测试用户数据还未写入完成,接口会返回400状态码(通常为用户不存在、凭证无效类错误),触发断言失败;当写入速度更快时测试就会正常通过,这就是随机失败的直接原因。
  • 清理逻辑存在异步隐患:after钩子中的User.deleteMany()操作同样没有等待执行完成就结束钩子,可能导致不同测试文件、用例之间出现测试数据污染,进一步提升随机失败的概率。
  • 错误分支逻辑存在漏洞:接口请求回调中if (error) done(error)执行后没有终止后续逻辑,当请求本身抛出错误时,后续的状态码断言依然会执行,会抛出未捕获的断言错误,干扰问题定位。

修复方案

  1. 补全before钩子的异步等待逻辑,确认测试用户写入数据库后再进入测试用例:
before(function (done) {
  let person = new User({
    email: "dev.test@test.com",
    password: "<hashed password>",
    firstName: "dev",
    lastName: "test",
    jobPosition: "Web Developer",
  });
  // 等待save操作执行完成再调用done
  person.save(function(err) {
    if (err) return done(err);
    done();
  });
});

如果项目支持async/await写法,也可以简化为:

before(async function () {
  let person = new User({
    email: "dev.test@test.com",
    password: "<hashed password>",
    firstName: "dev",
    lastName: "test",
    jobPosition: "Web Developer",
  });
  await person.save();
});
  1. 补全after钩子的异步等待逻辑,确认测试数据清理完成后再结束钩子:
after(function (done) {
  User.deleteMany({}, function (err) {
    if (err) return done(err);
    done();
  });
});

对应async写法:

after(async function () {
  await User.deleteMany({});
});
  1. 修正请求回调的错误分支,触发错误后直接返回,避免后续断言逻辑执行:
.end(function (error, response) {
  if (error) return done(error);

  response.should.have.status(200);
  response.body.should.have.property("token");
  done();
});

完成以上修改后,测试用例的执行顺序会和异步逻辑严格对齐,不会再出现因数据未就绪导致的随机失败问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 14:54:37