我有一个 Node.js Express 应用程序,我正在尝试使用 vitest 和 supertest 进行测试。但是,我遇到了缺少环境变量的问题。这是我的设置:
环境配置(environment.ts):
import z from 'zod';
const envSchema = z.object({
HOST: z
.string()
.trim()
.refine(
host => host.startsWith('http') || host.startsWith('https'),
'Invalid URL format',
),
PORT: z.coerce
.number({
description:
'.env files convert numbers to strings, therefore we have to enforce them to be numbers',
})
.positive()
.max(65536, `options.port should be >= 0 and < 65536`)
.default(3000),
BASE_URL: z.string().trim().min(1).default('/api/v1'),
DATABASE_URL: z
.string()
.url()
.trim()
.refine(url => url.startsWith('mongodb://'), 'Invalid Database URL format'),
GITHUB_API_URL: z
.string()
.url()
.trim()
.refine(url => url.startsWith('https://'), 'Invalid Github URL format')
.default('https://api.github.com'),
GITHUB_SECRET: z.string().trim().min(1),
LOG_DIR: z.string().optional(),
CLIENT_URL: z
.string()
.url()
.trim()
.refine(url => url.startsWith('http://'), 'Invalid client URL format')
.default('http://localhost:5173/'),
CORS_ORIGIN: z.string().trim().min(1).default('*'),
CORS_CREDENTIALS: z.boolean().default(true),
NODE_ENV: z
.enum(['development', 'production', 'test'])
.default('development'),
});
const envServer = envSchema.safeParse({
HOST: process.env.HOST,
PORT: process.env.PORT,
BASE_URL: process.env.BASE_URL,
DATABASE_URL: process.env.DATABASE_URL,
REDIS_HOST: process.env.REDIS_HOST,
REDIS_PORT: process.env.REDIS_PORT,
REDIS_TTL: process.env.REDIS_TTL,
REDIS_TIMEOUT: process.env.REDIS_TIMEOUT,
GITHUB_API_URL: process.env.GITHUB_API_URL,
GITHUB_SECRET: process.env.GITHUB_SECRET,
LOG_DIR: process.env.LOG_DIR,
CLIENT_URL: process.env.CLIENT_URL,
CORS_ORIGIN: process.env.CORS_ORIGIN,
CORS_CREDENTIALS: process.env.CORS_CREDENTIALS,
NODE_ENV: process.env.NODE_ENV,
});
if (!envServer.success) {
console.error(envServer.error.issues);
throw new Error('There is an error with the server environment variables');
}
export const serverSchema = envServer.data;
服务器设置(server.ts):
import compression from 'compression';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import { Application, json, urlencoded } from 'express';
import helmet from 'helmet';
import hpp from 'hpp';
import swaggerJSDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import { ErrorMiddleware } from '@/libs/shared/middlewares/error.middleware';
import logger from '@/utils/logger';
import { MongoDBInstance as dbConnection } from '@/config/database';
import { serverSchema } from '@/config/environment';
import applicationRoutes from '@/routes/index';
export class Server {
private readonly app: Application;
constructor(app: Application) {
this.app = app;
}
public start(): void {
this.connectDatabase();
this.securityMiddleware(this.app);
this.routesMiddleware(this.app);
this.globalErrorHandler(this.app);
this.startServer(this.app);
this.initializeSwagger();
}
public getServer() {
return this.app;
}
private securityMiddleware(app: Application): void {
app.use(hpp());
app.use(helmet());
app.use(compression());
app.use(json());
app.use(urlencoded({ extended: true, limit: '50mb' }));
app.use(cookieParser());
app.use(
cors({
origin: serverSchema.CLIENT_URL,
credentials: serverSchema.CORS_CREDENTIALS,
optionsSuccessStatus: 200,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
}),
);
}
private routesMiddleware(app: Application): void {
applicationRoutes(app);
}
private globalErrorHandler(app: Application): void {
app.use(ErrorMiddleware);
}
private connectDatabase(): void {
dbConnection.getInstance();
}
private startServer(app: Application): void {
logger.info(`------ NODE_ENV: ${serverSchema.NODE_ENV} ------`);
logger.info(`Server has started with process ${process.pid}`);
app.listen(serverSchema.PORT, () => {
logger.info(`Server listening on port ${serverSchema.PORT}`);
});
}
private initializeSwagger() {
const options = {
swaggerDefinition: {
openapi: '3.0.3',
info: {
title: 'API Documentation',
version: '1.0.0',
description: 'REST API docs',
},
servers: [
{
url: `${serverSchema.HOST}:${serverSchema.PORT}${serverSchema.BASE_URL}`,
},
],
},
apis: ['./swagger.yaml'],
};
const swaggerSpecs = swaggerJSDoc(options);
this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
logger.info(
`Docs are available at ${serverSchema.HOST}:${serverSchema.PORT}/api-docs`,
);
}
}
健康控制器(health.controller.ts):
import express, { Application } from 'express';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
import HealthController from '@/controllers/health.controller';
import { Server } from '@/server';
describe('HealthController', () => {
let app: Application;
beforeAll(() => {
app = new Server(express()).getServer();
const controller = new HealthController();
app.get('/health', controller.getHealth);
});
it('GET /health should return status 200 and { health: "OK!" } when GET /health', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ health: 'OK!' });
});
});
路线设置(health.router.ts):
import express, { Router } from 'express';
import HealthController from '@/controllers/health.controller';
export class HealthRoutes {
private router: Router;
constructor() {
this.router = express.Router();
}
public getRoutes(): Router {
const controller = new HealthController();
this.router.get('/health', controller.getHealth);
return this.router;
}
}
export const healthRoutes = new HealthRoutes();
路由器索引.ts:
import { Application } from 'express';
import { serverSchema } from '@/config/environment';
import { healthRoutes } from './health.router';
const applicationRoutes = (app: Application) => {
const routes = () => {
app.use(serverSchema.BASE_URL, healthRoutes.getRoutes());
};
routes();
};
export default applicationRoutes;
当我运行测试时,出现以下错误:
stderr | src/config/environment.ts:65:11
[
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'HOST' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'DATABASE_URL' ],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [ 'GITHUB_SECRET' ],
message: 'Required'
}
]
似乎某些环境变量丢失或定义不正确。如何确保在测试和应用程序运行时正确加载所有必需的环境变量?
所有环境变量都在我的 .env.development.local 文件中设置。 我在 package.json 脚本中使用 --env-file 来加载环境变量。 zod 模式正确定义并验证变量。
如何确保我的 Node.js 应用程序在测试和运行时正确加载并验证所有必需的环境变量?
我遗漏了什么或做错了什么?
好吧,我刚刚发现错误发生是因为我没有为 zod 模式中的那些 env 变量设置默认值。
我添加了默认值并且测试正确通过。