如何解决带有 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 举报,一经查实,本站将立刻删除。