3、数据库集成与故障模拟:配置TypeORM/Prisma测试数据库、使用事务回滚隔离测试数据、模拟数据库连接超时与断连
数据库是后端压测中最核心的依赖之一。在集成测试中,数据库配置不当会导致测试数据污染、测试间相互干扰,甚至压测结果失真。本章聚焦三个关键能力:测试数据库的独立配置、事务回滚隔离机制、以及数据库故障的模拟方法。掌握这些能力,才能让压测场景既真实又安全。
3.1 测试数据库的独立配置策略
在 NestJS 集成测试中,必须为测试环境配置独立的数据库实例或数据库名,避免与开发/生产数据库交叉。以下是 TypeORM 和 Prisma 的典型配置方式对比:
| 维度 | TypeORM | Prisma |
|---|---|---|
| 配置方式 | 通过 DataSource 工厂函数动态创建 |
通过 .env.test 或 PrismaClient 实例化时传入 |
| 数据库命名 | 推荐使用 test_${数据库名} 前缀 |
推荐使用 test_${数据库名} 前缀 |
| 连接池大小 | 建议 poolSize: 5 避免压测时连接耗尽 |
通过 connectionLimit 参数控制 |
| 自动建表 | synchronize: true(仅测试环境) |
prisma migrate deploy 或 db push |
| 日志级别 | 建议关闭或设为 error 级别 |
建议关闭或设为 warn 级别 |
关键原则:测试数据库必须与开发数据库物理隔离。压测场景中,建议每次运行测试前重建数据库结构,确保基线一致。
TypeORM 测试数据库配置示例
// test/database/test-datasource.ts
import { DataSource } from 'typeorm';
export const createTestDataSource = async (): Promise<DataSource> => {
const dataSource = new DataSource({
type: 'postgres',
host: process.env.TEST_DB_HOST || 'localhost',
port: Number(process.env.TEST_DB_PORT) || 5432,
username: process.env.TEST_DB_USER || 'test_user',
password: process.env.TEST_DB_PASS || 'test_pass',
database: process.env.TEST_DB_NAME || 'test_myapp',
entities: ['src/**/*.entity.ts'],
synchronize: true, // 测试环境自动同步表结构
logging: false, // 压测时关闭日志
poolSize: 5, // 限制连接池大小
});
await dataSource.initialize();
return dataSource;
};
Prisma 测试数据库配置示例
// test/database/test-prisma.ts
import { PrismaClient } from '@prisma/client';
export const createTestPrisma = (): PrismaClient => {
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.TEST_DATABASE_URL ||
'postgresql://test_user:test_pass@localhost:5432/test_myapp',
},
},
log: ['error'], // 压测时只记录错误
});
return prisma;
};
3.2 事务回滚隔离测试数据
压测场景中,每个测试用例执行后必须清理数据,否则后续用例会受到污染。事务回滚是最高效的隔离方式:每个测试用例在一个数据库事务中执行,测试结束时回滚事务,数据自动恢复到测试前状态。
性能优势:事务回滚比逐条 DELETE 快 10~50 倍,尤其适合压测场景中大量数据写入的测试。
TypeORM 事务回滚实现
// test/helpers/typeorm-transaction.ts
import { DataSource, QueryRunner } from 'typeorm';
export class TransactionHelper {
private queryRunner: QueryRunner;
async start(dataSource: DataSource): Promise<void> {
this.queryRunner = dataSource.createQueryRunner();
await this.queryRunner.connect();
await this.queryRunner.startTransaction();
}
async rollback(): Promise<void> {
if (this.queryRunner) {
await this.queryRunner.rollbackTransaction();
await this.queryRunner.release();
}
}
getManager() {
return this.queryRunner.manager;
}
}
Prisma 事务回滚实现
// test/helpers/prisma-transaction.ts
import { PrismaClient } from '@prisma/client';
export class PrismaTransactionHelper {
private prisma: PrismaClient;
constructor(prisma: PrismaClient) {
this.prisma = prisma;
}
async executeInTransaction<T>(fn: (tx: any) => Promise<T>): Promise<T> {
// Prisma 的交互式事务支持回滚
return this.prisma.$transaction(async (tx) => {
return fn(tx);
}, {
maxWait: 5000, // 最大等待时间
timeout: 10000, // 事务超时时间
});
}
}
注意:Prisma 的交互式事务在回滚时不会自动抛出异常,需要在回调函数中手动
throw 来触发回滚。建议封装统一的回滚辅助函数。
3.3 模拟数据库连接超时与断连
压测场景必须覆盖数据库故障情况,验证应用在数据库不可用时的降级和恢复能力。以下是两种核心故障模拟方式:
3.3.1 模拟连接超时
通过配置数据库驱动层的超时参数,可以模拟连接超时场景。以下是 TypeORM 和 Prisma 的超时配置:
// TypeORM 连接超时配置
const dataSource = new DataSource({
type: 'postgres',
// ... 其他配置
extra: {
connectionTimeoutMillis: 1000, // 1秒超时
query_timeout: 2000, // 查询超时
statement_timeout: 3000, // 语句超时
},
});
// Prisma 连接超时配置
const prisma = new PrismaClient({
datasources: { db: { url: process.env.DATABASE_URL } },
// Prisma 5.x 支持连接超时
connectionTimeout: 1000, // 1秒超时
});
压测场景设计:在压测过程中动态修改数据库连接的超时参数,观察应用是否能够正确抛出
TimeoutError 并触发降级逻辑。
3.3.2 模拟数据库断连
断连模拟需要更底层的操作,常见方法包括:
- 网络层断连:使用
iptables或tc工具阻断数据库端口 - 数据库层断连:执行
pg_terminate_backend或KILL CONNECTION - 应用层断连:在测试中手动关闭数据库连接池
// 使用 TypeORM 模拟断连
async function simulateDisconnection(dataSource: DataSource) {
// 关闭所有连接
await dataSource.destroy();
// 验证应用是否进入降级模式
// 然后重新初始化
await dataSource.initialize();
}
// 使用 Prisma 模拟断连
async function simulatePrismaDisconnection(prisma: PrismaClient) {
// 断开连接
await prisma.$disconnect();
// 验证降级逻辑
// 重新连接
await prisma.$connect();
}
3.4 故障恢复验证清单
在压测场景中,数据库故障恢复需要验证以下关键指标:
| 验证项 | 预期行为 | 验证方法 |
|---|---|---|
| 连接超时处理 | 返回 503 或自定义错误码 | 断言 HTTP 状态码和错误消息 |
| 断连后自动重连 | 在 5 秒内恢复连接 | 检查健康检查端点状态 |
| 事务回滚完整性 | 数据完全恢复到测试前 | 对比测试前后数据行数 |
| 降级策略生效 | 缓存数据返回而非数据库查询 | 检查缓存命中率指标 |
| 熔断器状态 | 故障时打开,恢复后关闭 | 监控熔断器状态变更日志 |
最佳实践:在压测场景中,建议将数据库故障模拟作为独立测试套件运行,与正常压测场景分开。故障模拟场景的并发量建议控制在 10~50 并发,避免真实数据库被压垮。
3.5 完整测试示例:事务回滚 + 故障模拟
以下是一个完整的 NestJS 集成测试示例,同时包含事务回滚和故障模拟:
// test/scenarios/database-fault.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { AppModule } from '../src/app.module';
import { TransactionHelper } from '../helpers/typeorm-transaction';
import { DataSource } from 'typeorm';
describe('Database Fault Recovery', () => {
let app: INestApplication;
let dataSource: DataSource;
let transactionHelper: TransactionHelper;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
dataSource = app.get(DataSource);
transactionHelper = new TransactionHelper();
});
beforeEach(async () => {
await transactionHelper.start(dataSource);
});
afterEach(async () => {
await transactionHelper.rollback();
});
it('should handle connection timeout gracefully', async () => {
// 模拟连接超时
const originalTimeout = dataSource.options.extra?.connectionTimeoutMillis;
Object.assign(dataSource.options, {
extra: { connectionTimeoutMillis: 1 }, // 1ms 超时
});
try {
await dataSource.query('SELECT 1');
fail('Should have thrown timeout error');
} catch (error) {
expect(error.message).toContain('timeout');
}
// 恢复配置
Object.assign(dataSource.options, {
extra: { connectionTimeoutMillis: originalTimeout },
});
});
it('should recover after disconnection', async () => {
// 模拟断连
await dataSource.destroy();
// 验证降级逻辑
const healthCheck = await app.get('HealthCheckService').check();
expect(healthCheck.database).toBe('unhealthy');
// 恢复连接
await dataSource.initialize();
// 验证恢复
const healthCheckAfter = await app.get('HealthCheckService').check();
expect(healthCheckAfter.database).toBe('healthy');
});
});
重要提醒:故障模拟测试必须在隔离的测试数据库上执行,切勿在生产数据库或共享开发数据库上运行。建议使用 Docker 容器化的临时数据库实例。
通过本章的配置和示例,你已经掌握了在 NestJS 集成测试中配置独立测试数据库、使用事务回滚隔离数据、以及模拟数据库连接超时与断连的核心技术。这些能力是构建可靠后端压测场景的基础,确保压测结果真实可信,同时保护测试环境的数据安全。