问题:
运行 jest 和 supertest 时,我在达到我描述的实际测试之前收到错误。服务器使用启动脚本运行良好,应用程序已定义。但运行测试脚本时,app 未定义。
背景:
- 我对 TypeScript 还很陌生,这是我第一次使用任何类型的测试。
- 我想分离服务器实例,如几篇博客文章和教程中所示,因为我计划在不同的文件中运行多个测试。
如果您有任何建议,即使我已经尝试过,我也会再试一次并让您知道。我束手无策,所以非常感谢任何帮助。谢谢。
错误:
FAIL src/components/users/user.test.ts
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'listen')
6 |
7 | dbConnection();
> 8 | export const server = app.listen(config.server.port, () => {
| ^
9 | logger.info(`Server is running on port: ${config.server.port}`);
10 | });
11 |
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1)
at Object.<anonymous> (src/library/errorHandler/errorHandler.ts:2:1)
at Object.<anonymous> (src/middleware/validateSchema.ts:3:1)
at Object.<anonymous> (src/components/users/routes.ts:2:1)
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
user.test.ts
import request from 'supertest';
import app from '../../server';
describe('User registration', () => {
it('POST /register --> return new user instance', async () => {
await request(app) // error occurs when reaching this point
.post('/user/register')
.send({
firstName: 'Thomas',
lastName: 'Haek',
email: '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a1d5c9ceccc0d2c9c0c4cae1c6ccc0c8cd8fc2cecc" rel="noreferrer noopener nofollow">[email protected]</a>',
password: '12345678aA',
confirmPassword: '12345678aA'
})
.expect(201)
.then((response) => {
expect(response.body).toEqual(
expect.objectContaining({
_id: expect.any(String),
firstName: expect.any(String),
lastName: expect.any(String),
email: expect.any(String),
token: expect.any(String)
})
);
});
});
});
server.ts
import express, { Application } from 'express';
import userRouter from './components/users/routes';
import { routeErrorHandler } from './middleware/errorHandler';
import httpLogger from './middleware/httpLogger';
import './process';
const app: Application = express();
app.use(httpLogger);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/user', userRouter);
app.use(routeErrorHandler);
export default app
index.ts
import { createHttpTerminator } from 'http-terminator';
import config from './config/config';
import dbConnection from './config/dbConnection';
import logger from './library/logger';
import app from './server'
dbConnection();
export const server = app.listen(config.server.port, () => {
logger.info(`Server is running on port: ${config.server.port}`);
});
export const httpTerminator = createHttpTerminator({ server });
package.json 脚本
"scripts": {
"test": "env-cmd -f ./src/config/test.env jest --watchAll",
"start": "env-cmd -f ./src/config/dev.env node build/index.js",
},
tsconfig.json
{
"compilerOptions": {
"outDir": "./build",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
},
"include": ["src/**/*"]
}
jest.config.ts
import { Config } from 'jest';
/** @type {import('ts-jest').JestConfigWithTsJest} */
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['./src'],
moduleFileExtensions: ['js', 'ts'],
clearMocks: true,
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/src/config/'],
coverageProvider: 'v8',
coverageReporters: ['json', 'text', 'lcov', 'clover'],
verbose: true
};
export default config;
exitHandler.ts
import mongoose from 'mongoose';
import { httpTerminator, server } from '../..';
import logger from '../logger';
class ExitHandler {
public async handleExit(code: number, timeout = 5000): Promise<void> {
try {
logger.info(`Attempting graceful shutdown with code: ${code}`);
setTimeout(() => {
logger.info(`Forcing a shutdown with code: ${code}`);
process.exit(code);
}, timeout).unref();
if (server.listening) {
logger.info('Terminating HTTP connections');
await httpTerminator.terminate();
await mongoose.connection.close();
}
logger.info(`Exiting gracefully with code ${code}`);
process.exit(code);
} catch (error) {
logger.error(error);
logger.error(
`Unable to shutdown gracefully... Forcing exit with code: ${code}`
);
process.exit(code);
}
}
}
export const exitHandler = new ExitHandler();
我尝试过的事情:
- 对测试和服务器脚本使用相同的环境文件(相同的错误)
- 搞乱 tsconfig 和 jest 配置文件(同样的错误)
- 使用 module.exports = app 而不是导出默认应用程序或导出 const server = app(相同的错误)
- 注释掉所有中间件和路由并仅导出应用程序(相同的错误)
最佳答案
我相信这是由循环依赖引起的。从错误堆栈中
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1
…
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
我发现 server.ts
依赖于 exitHandler.ts
,而 exitHandler.ts
又依赖于 index.ts
。但在 index.ts
中,您从“./server”导入应用
形成一个圆圈。
更具体地说,为了从 server.ts
创建 app
,它需要 exitHandler
,但那个东西需要 索引
,并且索引
需要服务器
。这就像没有基本情况返回的递归。与不定函数递归不同,依赖解析只会为您提供 app
作为 undefined
。
因此你需要打破这个循环。使用一些依赖注入(inject)技巧来打破 exitHandler
和 index
之间的联系即可。
如果您不知道如何执行此操作,请发布 exitHandler.ts
代码,我会跟进。
不要使用 import { httpTerminator, server } from '../..';
试试这个:
let server, httpTerminator;
export function injectDependency(s, h) {
server = s;
httpTerminator = h;
}
现在在index.ts
import { injectDependency } from "…/exitHandler"
injectDependency(server, httpTerminator);
关于node.js - 类型错误 : Cannot read properties of undefined (reading 'listen' ) when testing with Jest, super 测试、Express、Typescript,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75042451/