753 lines
18 KiB
Markdown
753 lines
18 KiB
Markdown
# Wallet Service 测试文档
|
|
|
|
## 概述
|
|
|
|
本文档描述 Wallet Service 的测试架构和实现方式,包括单元测试、集成测试和端到端 (E2E) 测试。
|
|
|
|
---
|
|
|
|
## 测试架构
|
|
|
|
```
|
|
测试金字塔
|
|
┌─────────────┐
|
|
│ E2E 测试 │ ← 少量,真实数据库
|
|
│ (23 个) │
|
|
├─────────────┤
|
|
│ 集成测试 │ ← 应用服务层
|
|
│ (Mock) │
|
|
├─────────────────┤
|
|
│ 单元测试 │ ← 领域层,快速
|
|
│ (46 个) │
|
|
└─────────────────┘
|
|
```
|
|
|
|
### 测试类型分布
|
|
|
|
| 测试类型 | 测试数量 | 覆盖范围 | 执行时间 |
|
|
|---------|---------|---------|---------|
|
|
| 单元测试 | 46 | 领域层 (Value Objects, Aggregates) | ~2s |
|
|
| 应用服务测试 | 23 | 应用层 (Service, Commands, Queries) | ~3s |
|
|
| E2E 测试 | 23 | API 层 + 数据库 | ~7s |
|
|
|
|
---
|
|
|
|
## 目录结构
|
|
|
|
```
|
|
wallet-service/
|
|
├── src/
|
|
│ ├── domain/
|
|
│ │ ├── value-objects/
|
|
│ │ │ ├── money.vo.spec.ts # Money 值对象测试
|
|
│ │ │ ├── balance.vo.spec.ts # Balance 值对象测试
|
|
│ │ │ └── hashpower.vo.spec.ts # Hashpower 值对象测试
|
|
│ │ └── aggregates/
|
|
│ │ ├── wallet-account.aggregate.spec.ts
|
|
│ │ ├── ledger-entry.aggregate.spec.ts
|
|
│ │ ├── deposit-order.aggregate.spec.ts
|
|
│ │ └── settlement-order.aggregate.spec.ts
|
|
│ └── application/
|
|
│ └── services/
|
|
│ └── wallet-application.service.spec.ts
|
|
├── test/
|
|
│ ├── jest-e2e.json # E2E 测试配置
|
|
│ ├── app.e2e-spec.ts # 主 E2E 测试套件
|
|
│ └── simple.e2e-spec.ts # 简单连接测试
|
|
└── jest.config.js # Jest 配置
|
|
```
|
|
|
|
---
|
|
|
|
## 单元测试
|
|
|
|
### 值对象测试
|
|
|
|
值对象测试确保业务规则在值对象层面正确实现。
|
|
|
|
#### Money 值对象测试
|
|
|
|
```typescript
|
|
// src/domain/value-objects/money.vo.spec.ts
|
|
describe('Money Value Object', () => {
|
|
describe('creation', () => {
|
|
it('should create USDT money', () => {
|
|
const money = Money.USDT(100);
|
|
expect(money.value).toBe(100);
|
|
expect(money.currency).toBe('USDT');
|
|
});
|
|
|
|
it('should throw on negative amount', () => {
|
|
expect(() => Money.USDT(-10)).toThrow('negative');
|
|
});
|
|
});
|
|
|
|
describe('operations', () => {
|
|
it('should add same currency', () => {
|
|
const a = Money.USDT(100);
|
|
const b = Money.USDT(50);
|
|
const sum = a.add(b);
|
|
expect(sum.value).toBe(150);
|
|
});
|
|
|
|
it('should throw on currency mismatch', () => {
|
|
const usdt = Money.USDT(100);
|
|
const bnb = Money.BNB(1);
|
|
expect(() => usdt.add(bnb)).toThrow('currency');
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
#### Balance 值对象测试
|
|
|
|
```typescript
|
|
// src/domain/value-objects/balance.vo.spec.ts
|
|
describe('Balance Value Object', () => {
|
|
it('should freeze available to frozen', () => {
|
|
const balance = Balance.create(Money.USDT(100), Money.USDT(0));
|
|
const frozen = balance.freeze(Money.USDT(30));
|
|
|
|
expect(frozen.available.value).toBe(70);
|
|
expect(frozen.frozen.value).toBe(30);
|
|
});
|
|
|
|
it('should throw on insufficient balance for freeze', () => {
|
|
const balance = Balance.create(Money.USDT(50), Money.USDT(0));
|
|
expect(() => balance.freeze(Money.USDT(100))).toThrow('Insufficient');
|
|
});
|
|
});
|
|
```
|
|
|
|
### 聚合测试
|
|
|
|
聚合测试验证复杂的业务逻辑和领域事件。
|
|
|
|
#### WalletAccount 聚合测试
|
|
|
|
```typescript
|
|
// src/domain/aggregates/wallet-account.aggregate.spec.ts
|
|
describe('WalletAccount Aggregate', () => {
|
|
let wallet: WalletAccount;
|
|
|
|
beforeEach(() => {
|
|
wallet = WalletAccount.createNew(UserId.create(1));
|
|
});
|
|
|
|
describe('deposit', () => {
|
|
it('should increase USDT balance on deposit', () => {
|
|
const amount = Money.USDT(100);
|
|
wallet.deposit(amount, 'KAVA', 'tx_hash_123');
|
|
|
|
expect(wallet.balances.usdt.available.value).toBe(100);
|
|
expect(wallet.domainEvents.length).toBe(1);
|
|
expect(wallet.domainEvents[0].eventType).toBe('DepositCompletedEvent');
|
|
});
|
|
|
|
it('should throw error when wallet is frozen', () => {
|
|
wallet.freezeWallet();
|
|
expect(() => wallet.deposit(Money.USDT(100), 'KAVA', 'tx')).toThrow('Wallet');
|
|
});
|
|
});
|
|
|
|
describe('rewards lifecycle', () => {
|
|
it('should move pending rewards to settleable', () => {
|
|
const usdt = Money.USDT(10);
|
|
const hashpower = Hashpower.create(5);
|
|
const expireAt = new Date(Date.now() + 86400000);
|
|
|
|
wallet.addPendingReward(usdt, hashpower, expireAt, 'order_123');
|
|
wallet.movePendingToSettleable();
|
|
|
|
expect(wallet.rewards.pendingUsdt.isZero()).toBe(true);
|
|
expect(wallet.rewards.settleableUsdt.value).toBe(10);
|
|
expect(wallet.hashpower.value).toBe(5);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
### 运行单元测试
|
|
|
|
```bash
|
|
# 运行所有单元测试
|
|
npm test
|
|
|
|
# 监听模式
|
|
npm run test:watch
|
|
|
|
# 覆盖率报告
|
|
npm run test:cov
|
|
|
|
# 运行特定文件
|
|
npm test -- money.vo.spec.ts
|
|
|
|
# 运行特定测试
|
|
npm test -- --testNamePattern="should create USDT"
|
|
```
|
|
|
|
---
|
|
|
|
## 应用服务测试
|
|
|
|
应用服务测试使用 Mock 仓储来隔离业务逻辑测试。
|
|
|
|
### Mock 仓储设置
|
|
|
|
```typescript
|
|
// src/application/services/wallet-application.service.spec.ts
|
|
describe('WalletApplicationService', () => {
|
|
let service: WalletApplicationService;
|
|
let mockWalletRepo: any;
|
|
let mockLedgerRepo: any;
|
|
let mockDepositRepo: any;
|
|
|
|
beforeEach(async () => {
|
|
// 创建 Mock 仓储
|
|
mockWalletRepo = {
|
|
save: jest.fn(),
|
|
findByUserId: jest.fn(),
|
|
getOrCreate: jest.fn(),
|
|
};
|
|
|
|
mockLedgerRepo = {
|
|
save: jest.fn(),
|
|
findByUserId: jest.fn(),
|
|
};
|
|
|
|
mockDepositRepo = {
|
|
save: jest.fn(),
|
|
existsByTxHash: jest.fn(),
|
|
};
|
|
|
|
// 配置测试模块
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
WalletApplicationService,
|
|
{ provide: WALLET_ACCOUNT_REPOSITORY, useValue: mockWalletRepo },
|
|
{ provide: LEDGER_ENTRY_REPOSITORY, useValue: mockLedgerRepo },
|
|
{ provide: DEPOSIT_ORDER_REPOSITORY, useValue: mockDepositRepo },
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<WalletApplicationService>(WalletApplicationService);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 命令处理测试
|
|
|
|
```typescript
|
|
describe('handleDeposit', () => {
|
|
it('should process deposit successfully', async () => {
|
|
const command = new HandleDepositCommand('1', 100, ChainType.KAVA, 'tx_123');
|
|
const mockWallet = createMockWallet(BigInt(1), 0);
|
|
|
|
mockDepositRepo.existsByTxHash.mockResolvedValue(false);
|
|
mockWalletRepo.getOrCreate.mockResolvedValue(mockWallet);
|
|
mockDepositRepo.save.mockResolvedValue({});
|
|
mockWalletRepo.save.mockResolvedValue(mockWallet);
|
|
mockLedgerRepo.save.mockResolvedValue({});
|
|
|
|
await service.handleDeposit(command);
|
|
|
|
expect(mockDepositRepo.existsByTxHash).toHaveBeenCalledWith('tx_123');
|
|
expect(mockWalletRepo.getOrCreate).toHaveBeenCalledWith(BigInt(1));
|
|
expect(mockDepositRepo.save).toHaveBeenCalled();
|
|
expect(mockWalletRepo.save).toHaveBeenCalled();
|
|
expect(mockLedgerRepo.save).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw error for duplicate transaction', async () => {
|
|
mockDepositRepo.existsByTxHash.mockResolvedValue(true);
|
|
const command = new HandleDepositCommand('1', 100, ChainType.KAVA, 'tx_dup');
|
|
|
|
await expect(service.handleDeposit(command)).rejects.toThrow('Duplicate');
|
|
});
|
|
});
|
|
```
|
|
|
|
### 查询处理测试
|
|
|
|
```typescript
|
|
describe('getMyWallet', () => {
|
|
it('should return wallet DTO', async () => {
|
|
const query = new GetMyWalletQuery('1');
|
|
const mockWallet = createMockWallet(BigInt(1), 100);
|
|
|
|
mockWalletRepo.getOrCreate.mockResolvedValue(mockWallet);
|
|
|
|
const result = await service.getMyWallet(query);
|
|
|
|
expect(result.userId).toBe('1');
|
|
expect(result.balances.usdt.available).toBe(100);
|
|
expect(result.status).toBe('ACTIVE');
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## E2E 测试
|
|
|
|
E2E 测试使用真实数据库验证完整的请求流程。
|
|
|
|
### 测试配置
|
|
|
|
```json
|
|
// test/jest-e2e.json
|
|
{
|
|
"moduleFileExtensions": ["js", "json", "ts"],
|
|
"rootDir": ".",
|
|
"testEnvironment": "node",
|
|
"testRegex": ".e2e-spec.ts$",
|
|
"transform": {
|
|
"^.+\\.(t|j)s$": "ts-jest"
|
|
},
|
|
"moduleNameMapper": {
|
|
"^@/(.*)$": "<rootDir>/../src/$1"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 测试环境设置
|
|
|
|
```typescript
|
|
// test/app.e2e-spec.ts
|
|
describe('Wallet Service (e2e)', () => {
|
|
let app: INestApplication;
|
|
let prisma: PrismaService;
|
|
let authToken: string;
|
|
const testUserId = '99999';
|
|
const jwtSecret = process.env.JWT_SECRET || 'test-secret';
|
|
|
|
beforeAll(async () => {
|
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
|
|
app = moduleFixture.createNestApplication();
|
|
app.setGlobalPrefix('api/v1');
|
|
|
|
// 只需要 ValidationPipe
|
|
// DomainExceptionFilter 和 TransformInterceptor 由 AppModule 提供
|
|
app.useGlobalPipes(
|
|
new ValidationPipe({
|
|
whitelist: true,
|
|
forbidNonWhitelisted: true,
|
|
transform: true,
|
|
}),
|
|
);
|
|
|
|
prisma = app.get(PrismaService);
|
|
await app.init();
|
|
|
|
// 生成测试 JWT Token
|
|
authToken = jwt.sign(
|
|
{ sub: testUserId, seq: 1001 },
|
|
jwtSecret,
|
|
{ expiresIn: '1h' },
|
|
);
|
|
|
|
await cleanupTestData();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await cleanupTestData();
|
|
await app.close();
|
|
});
|
|
|
|
async function cleanupTestData() {
|
|
await prisma.ledgerEntry.deleteMany({ where: { userId: BigInt(testUserId) } });
|
|
await prisma.depositOrder.deleteMany({ where: { userId: BigInt(testUserId) } });
|
|
await prisma.walletAccount.deleteMany({ where: { userId: BigInt(testUserId) } });
|
|
}
|
|
});
|
|
```
|
|
|
|
### API 端点测试
|
|
|
|
```typescript
|
|
describe('Health Check', () => {
|
|
it('/api/v1/health (GET) - should return health status', () => {
|
|
return request(app.getHttpServer())
|
|
.get('/api/v1/health')
|
|
.expect(200)
|
|
.expect(res => {
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data.status).toBe('ok');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Wallet Operations', () => {
|
|
it('/api/v1/wallet/my-wallet (GET) - should return wallet info', async () => {
|
|
const res = await request(app.getHttpServer())
|
|
.get('/api/v1/wallet/my-wallet')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data).toHaveProperty('walletId');
|
|
expect(res.body.data).toHaveProperty('balances');
|
|
expect(res.body.data.status).toBe('ACTIVE');
|
|
});
|
|
});
|
|
|
|
describe('Deposit Operations', () => {
|
|
it('/api/v1/wallet/deposit (POST) - should process deposit', async () => {
|
|
const res = await request(app.getHttpServer())
|
|
.post('/api/v1/wallet/deposit')
|
|
.send({
|
|
userId: testUserId,
|
|
amount: 100,
|
|
chainType: 'KAVA',
|
|
txHash: `test_tx_${Date.now()}`,
|
|
})
|
|
.expect(201);
|
|
|
|
expect(res.body.success).toBe(true);
|
|
|
|
// 验证余额更新
|
|
const walletRes = await request(app.getHttpServer())
|
|
.get('/api/v1/wallet/my-wallet')
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(walletRes.body.data.balances.usdt.available).toBe(100);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 数据库完整性测试
|
|
|
|
```typescript
|
|
describe('Database Integrity', () => {
|
|
it('should persist wallet data correctly', async () => {
|
|
const wallet = await prisma.walletAccount.findFirst({
|
|
where: { userId: BigInt(testUserId) },
|
|
});
|
|
|
|
expect(wallet).not.toBeNull();
|
|
expect(wallet?.status).toBe('ACTIVE');
|
|
expect(Number(wallet?.usdtAvailable)).toBe(150);
|
|
});
|
|
|
|
it('should persist ledger entries correctly', async () => {
|
|
const entries = await prisma.ledgerEntry.findMany({
|
|
where: { userId: BigInt(testUserId) },
|
|
});
|
|
|
|
expect(entries.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## WSL2 环境配置
|
|
|
|
### PostgreSQL Docker 容器
|
|
|
|
```bash
|
|
# 在 WSL2 中启动 PostgreSQL
|
|
docker run -d \
|
|
--name wallet-postgres-test \
|
|
-e POSTGRES_USER=wallet \
|
|
-e POSTGRES_PASSWORD=wallet123 \
|
|
-e POSTGRES_DB=wallet_test \
|
|
-p 5432:5432 \
|
|
postgres:15-alpine
|
|
|
|
# 获取容器 IP
|
|
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' wallet-postgres-test
|
|
```
|
|
|
|
### 环境变量设置
|
|
|
|
```bash
|
|
# 使用容器 IP (推荐)
|
|
export DATABASE_URL="postgresql://wallet:wallet123@172.17.0.2:5432/wallet_test?schema=public"
|
|
|
|
# 或使用 localhost (确保端口映射正常)
|
|
export DATABASE_URL="postgresql://wallet:wallet123@localhost:5432/wallet_test?schema=public"
|
|
|
|
export JWT_SECRET="test-jwt-secret-key-for-e2e-testing"
|
|
```
|
|
|
|
### 运行 E2E 测试
|
|
|
|
```bash
|
|
# 初始化数据库
|
|
npx prisma generate
|
|
npx prisma db push
|
|
|
|
# 运行 E2E 测试
|
|
npm run test:e2e
|
|
|
|
# 使用 dotenv 加载环境变量
|
|
npx dotenv -e .env.test -- npm run test:e2e
|
|
```
|
|
|
|
---
|
|
|
|
## 常见问题
|
|
|
|
### 问题 1: 测试超时
|
|
|
|
**症状**: E2E 测试超时或长时间无响应
|
|
|
|
**原因**: WSL2 跨文件系统性能问题
|
|
|
|
**解决方案**: 将项目复制到 WSL2 原生文件系统
|
|
|
|
```bash
|
|
# 不要使用 /mnt/c/ 路径
|
|
cp -r /mnt/c/project ~/project
|
|
cd ~/project
|
|
npm install
|
|
npm run test:e2e
|
|
```
|
|
|
|
### 问题 2: 数据库连接失败
|
|
|
|
**症状**: `ECONNREFUSED` 或 `Connection timeout`
|
|
|
|
**原因**: 网络配置问题
|
|
|
|
**解决方案**: 使用 Docker 容器 IP 而非 localhost
|
|
|
|
```bash
|
|
# 获取容器 IP
|
|
docker inspect wallet-postgres-test | grep IPAddress
|
|
|
|
# 更新 DATABASE_URL
|
|
export DATABASE_URL="postgresql://wallet:wallet123@172.17.0.2:5432/wallet_test"
|
|
```
|
|
|
|
### 问题 3: 响应结构断言失败
|
|
|
|
**症状**: `expect(res.body.data).toHaveProperty('walletId')` 失败
|
|
|
|
**原因**: TransformInterceptor 被重复应用
|
|
|
|
**错误代码**:
|
|
```typescript
|
|
// 错误 - 导致双重包装
|
|
app.useGlobalFilters(new DomainExceptionFilter());
|
|
app.useGlobalInterceptors(new TransformInterceptor());
|
|
```
|
|
|
|
**正确代码**:
|
|
```typescript
|
|
// 正确 - 只添加 ValidationPipe
|
|
// Filter 和 Interceptor 由 AppModule 通过 APP_INTERCEPTOR 提供
|
|
app.useGlobalPipes(new ValidationPipe({...}));
|
|
```
|
|
|
|
### 问题 4: Prisma Client 未生成
|
|
|
|
**症状**: `Cannot find module '@prisma/client'`
|
|
|
|
**解决方案**:
|
|
```bash
|
|
npx prisma generate
|
|
```
|
|
|
|
---
|
|
|
|
## 测试覆盖率
|
|
|
|
### 生成覆盖率报告
|
|
|
|
```bash
|
|
npm run test:cov
|
|
```
|
|
|
|
### 覆盖率目标
|
|
|
|
| 层级 | 语句覆盖 | 分支覆盖 | 函数覆盖 |
|
|
|-----|---------|---------|---------|
|
|
| Domain Layer | 90%+ | 85%+ | 90%+ |
|
|
| Application Layer | 80%+ | 75%+ | 85%+ |
|
|
| API Layer | 70%+ | 65%+ | 80%+ |
|
|
|
|
### 查看报告
|
|
|
|
```bash
|
|
# HTML 报告
|
|
open coverage/lcov-report/index.html
|
|
|
|
# 终端摘要
|
|
npm run test:cov -- --coverageReporters="text-summary"
|
|
```
|
|
|
|
---
|
|
|
|
## CI/CD 集成
|
|
|
|
### GitHub Actions 配置
|
|
|
|
```yaml
|
|
# .github/workflows/test.yml
|
|
name: Tests
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
env:
|
|
POSTGRES_USER: wallet
|
|
POSTGRES_PASSWORD: wallet123
|
|
POSTGRES_DB: wallet_test
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Generate Prisma Client
|
|
run: npx prisma generate
|
|
|
|
- name: Push database schema
|
|
run: npx prisma db push
|
|
env:
|
|
DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
|
|
|
|
- name: Run unit tests
|
|
run: npm test
|
|
|
|
- name: Run E2E tests
|
|
run: npm run test:e2e
|
|
env:
|
|
DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
|
|
JWT_SECRET: test-jwt-secret
|
|
|
|
- name: Upload coverage
|
|
uses: codecov/codecov-action@v3
|
|
```
|
|
|
|
---
|
|
|
|
## 测试最佳实践
|
|
|
|
### 1. 测试命名
|
|
|
|
```typescript
|
|
// 好的命名
|
|
it('should increase USDT balance when deposit is processed')
|
|
it('should throw InsufficientBalanceError when balance is insufficient')
|
|
|
|
// 避免的命名
|
|
it('test deposit')
|
|
it('works')
|
|
```
|
|
|
|
### 2. 测试隔离
|
|
|
|
```typescript
|
|
// 每个测试独立
|
|
beforeEach(() => {
|
|
wallet = WalletAccount.createNew(UserId.create(1));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanupTestData();
|
|
});
|
|
```
|
|
|
|
### 3. 使用工厂函数
|
|
|
|
```typescript
|
|
// 创建测试数据的工厂函数
|
|
const createMockWallet = (userId: bigint, balance = 0) => {
|
|
return WalletAccount.reconstruct({
|
|
walletId: BigInt(1),
|
|
userId,
|
|
usdtAvailable: new Decimal(balance),
|
|
// ...
|
|
});
|
|
};
|
|
```
|
|
|
|
### 4. 断言明确
|
|
|
|
```typescript
|
|
// 好的断言
|
|
expect(wallet.balances.usdt.available.value).toBe(100);
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data).toHaveProperty('walletId');
|
|
|
|
// 避免的断言
|
|
expect(wallet).toBeTruthy();
|
|
expect(res.body).toBeDefined();
|
|
```
|
|
|
|
---
|
|
|
|
## 调试测试
|
|
|
|
### VS Code 调试配置
|
|
|
|
```json
|
|
// .vscode/launch.json
|
|
{
|
|
"version": "0.2.0",
|
|
"configurations": [
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Debug Jest Tests",
|
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
"args": ["--runInBand", "--watchAll=false"],
|
|
"console": "integratedTerminal",
|
|
"internalConsoleOptions": "neverOpen"
|
|
},
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Debug E2E Tests",
|
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
|
"args": ["--config", "test/jest-e2e.json", "--runInBand"],
|
|
"console": "integratedTerminal",
|
|
"env": {
|
|
"DATABASE_URL": "postgresql://wallet:wallet123@localhost:5432/wallet_test",
|
|
"JWT_SECRET": "test-secret"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 单个测试调试
|
|
|
|
```bash
|
|
# 只运行特定测试
|
|
npm test -- --testNamePattern="should process deposit"
|
|
|
|
# 运行特定文件
|
|
npm test -- wallet-account.aggregate.spec.ts
|
|
|
|
# 显示详细输出
|
|
npm test -- --verbose
|
|
```
|
|
|