带有 redis sinon 存根的 mocha 测试在套件外抛出未捕获的错误

如何解决带有 redis sinon 存根的 mocha 测试在套件外抛出未捕获的错误

我无法让 Sinon 正确模拟 redis 以进行单元测试。测试用例都通过了,但是 Mocha 每次仍然因为 redis 连接错误而崩溃,我无法深入了解它。经过几天的工作并梳理 Stackoverflow,我仍然无法解决它,所以是时候问问比我更聪明的人了。

我从一个将导出应用程序的 server.ts 文件开始,因此它可以加载到外部以进行最终运行时配置或加载到测试套件中:

import express,{ Application } from "express";
import bodyParser from "body-parser";
import auth from "./routes/authRoutes";

const createServer = () => {
  const app: Application = express();

  app.use(bodyParser.json());

  app.get("/",(_req,res: Response,_next) => {
    res.json({
      message: "Hello World",});
  });

  app.use("/auth",auth);

  return app;
};

export default createServer;

authRoutes.ts 文件相当简单,为了简洁起见,我合并了验证中间件和端点逻辑:

import { Router,Request,Response,NextFunction } from "express";
import { check,validationResult } from "express-validator";
import redisCache from "../data/redis-cache";

const router = Router();

const postAuth = async (req: Request,_next: NextFunction) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(422).json(errors.array());
  }
  const sessionId = Math.random().toString();
  await redisCache.setAsync(sessionId,"session1");
  return res.status(200).json({ sessionId: sessionId });
};

router.post(
  "/login",[
    check("body").exists().withMessage("No Post Body"),],postAuth
);

export default router;

我还设置了一个 redis-cache.ts 文件来保证和导出我需要的 redis 函数

import redis from "redis";

import { promisify } from "util";

const client = redis.createClient({
  host: process.env.REdis_HOST || "localhost",port: process.env.REdis_PORT ? +process.env.REdis_PORT : 6379,});

const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);

export default {
  getAsync,setAsync,};

测试套件 index.spec.ts 很简单,并设置了 3 个测试:

import chai,{ expect } from "chai";
import chaiHttp from "chai-http";
import * as sinon from "sinon";
import createServer from "../src/server";
import redis from "redis";

chai.use(chaiHttp);
chai.should();

describe("auth routes",function () {
  const mock = sinon.createSandBox();

  before(function () {
    mock.stub(redis,"createClient").returns({
      set: (key: string,value: string,cb: (e: Error | null) => void) => {
        console.log(`mocked set,request: ${key} -> ${value}`);
        return cb(null);
      },get: (key: string,cb: (e: Error | null) => void) => {
        console.log(`mocked get,request: ${key} `);
        return cb(null);
      },quit: (_cb: () => void) => {
        console.log(`mocked quit method`);
      },} as any);
  });

  after(function () {
    mock.restore();
  });

  describe("#GET/login",function () {
    it("responds with 404",function (done) {
      const app = createServer();
      chai
        .request(app)
        .get("/auth/login")
        .end((err: any,res: any) => {
          expect(err).to.be.null;
          expect(res.status).to.equal(404);
          done();
        });
    });
  });

  describe("#POST/login",function () {
    const app = createServer();
    function requestSend() {
      return chai.request(app).post("/auth/login");
    }

    describe("without body",function () {
      it("responds with 422",function (done) {
        requestSend().end(function (err: any,res: any) {
          if (err) return done(err);
          expect(res.status).to.equal(422);
          done();
        });
      });

      it("returns error when no post body sent",res: any) {
          if (err) return done(err);
          expect(res.body).to.be.a("array");
          const body = res.body as { msg: string }[];
          const messageIndex = body.findindex(
            (el) => el.msg === "No Post Body"
          );
          expect(messageIndex).to.not.equal(-1);
          done();
        });
      });
    });
  });
});

现在我用 Mocha 运行测试并得到以下结果:

auth routes
    Uncaught error outside test suite
    #GET/login
      ✔ responds with 404
    #POST/login
      without body
        ✔ responds with 422 (45ms)
        ✔ returns error when no post body sent


  3 passing (217ms)
  1 failing

  1) auth routes
       Uncaught error outside test suite:
     Uncaught Redis connection to localhost:6379 Failed - connect ECONNREFUSED 127.0.0.1:6379
  Error: connect ECONNREFUSED 127.0.0.1:6379

如您所见,所有测试都通过了,但 redis 仍在尝试在测试套件之外进行连接,我不知道如何绕过它。如果我在我的 await redisCache.setAsync 文件中注释掉对 authRoutes.ts调用并运行测试,所有绿色且没有错误

我花了好几个小时在谷歌上搜索并尝试一些东西,但没有运气,我很确定我遗漏了一些东西,但我找不到。任何帮助将不胜感激。

我按照 this blog post 创建了一个类似的设置并且它有效,这让我相信这可以完成并且错误是我的部分。

解决方法

我能够用这个来回答我自己的问题(终于!)看来我需要为每个请求确定 redis 客户端的创建范围,并确保它在请求完成时在回调中退出。我借鉴了上面的博客文章并重建了 redis-cache.ts 文件,以另一种方式将 redis 调用转换为 Promise。我的新 redis-cache.ts 看起来像这样:

import * as redis from "redis";
import {
  SuccessfulSetResponse,FailResponse,SuccessfulGetResponse,} from "../types";

const url = `${process.env.REDIS_HOST || "localhost"}:${
  process.env.REDIS_PORT ? +process.env.REDIS_PORT : 6379
}`;

const getAsync = async (
  key: string
): Promise<SuccessfulGetResponse | FailResponse> => {
  return new Promise((resolve,_reject) => {
    const client = redis.createClient({ url });

    client.get(key,(error,value) => {
      // note the client quits in the callback
      client.quit();

      if (error) {
        resolve({
          reason: error.message,...error,} as FailResponse);
      }
      if (value === null) {
        resolve({
          success: false,} as SuccessfulGetResponse);
      }

      resolve({
        success: true,value: value,} as SuccessfulGetResponse);
    });
  });
};

const setAsync = async (
  key: string,value: string
): Promise<SuccessfulSetResponse | FailResponse> => {
  return new Promise((resolve,_reject) => {
    const client = redis.createClient({ url });

    client.set(key,value,(error) => {
      // note the client quits in the callback
      client.quit();

      if (error) {
        resolve({
          reason: error.message,} as FailResponse);
      }

      resolve({
        success: true,} as SuccessfulSetResponse);
    });
  });
};

export default {
  getAsync,setAsync,};

我还在 src/types.d.ts 文件中添加了一些类型定义:

export interface SuccessfulSetResponse {
  success: boolean;
}

export interface SuccessfulGetResponse {
  success: boolean;
  value?: string;
}

export interface FailResponse {
  reason: string;
  [key: string]: string;
}

通过这些更改,我只需要寻找几个使用它的地方(在用于缓存会话令牌的 auth 中间件中是目前唯一的东西)并且所有测试都是绿色的,并且手动运行应用程序并使用邮递员按预期工作。

我仍然很想知道到底发生了什么,如果有人知道和/或编辑此答案,请发表评论以提供有关为什么会这样工作的见解。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?