在端到端测试 NestJS 时使用测试数据库
Using test database when e2e-testing NestJS
在这个项目中,它使用了 NestJS 和 TypeORM。对于真正的 API 请求,CRUD 操作正在 MySQL(使用 AWS RDS)上运行。
现在我正在尝试使用 SQLite(内存中)来测试 API 结果。
我在单元测试中成功实现了这个,如下代码。
首先,下面是 create-memory-db.ts
,它 returns 连接到内存中的 SQLite 数据库。
type Entity = Function | string | EntitySchema<any>;
export async function createMemoryDB(entities: Entity[]) {
return createConnection({
type: 'sqlite',
database: ':memory:',
entities,
logging: false,
synchronize: true,
});
}
- 并且通过使用上面的导出函数,我成功地 运行 单元测试,如下所示。
describe('UserService Logic Test', () => {
let userService: UserService;
let connection: Connection;
let userRepository: Repository<User>;
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
// testing codes.
});
我正尝试在 e2e 测试中做同样的事情。我试过下面的代码。
// user.e2e-spec.ts
describe('UserController (e2e)', () => {
let userController: UserController;
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
userController = new UserController(userService);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [],
controllers: [UserController],
providers: [UserService],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
// await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
return request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect(HttpStatus.CREATED);
});
});
我无法创建 UserModule
,因为它没有带有 Connection
参数的构造函数。
代码本身没有编译错误,但是在执行e2e测试时得到如下结果。
Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index[0] is available in the RootTestModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current RootTestModule?
- If UserRepository is exported from a seperate @Module, is that module imported within RootTestModule?
@Module({
imports: [/* The module containing UserRepository */]
})
TypeError: Cannot read property 'getHttpServer' of undefined.
如有任何帮助,我们将不胜感激。谢谢:)
- 更新:尝试以下操作后出现新错误。
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [UserModule],
})
.overrideProvider(UserService)
.useClass(userService)
.compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', async () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
const result = await request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect({ status: HttpStatus.CREATED });
});
});
- 我检查了查询是否有效,并且能够看到它正在按我的需要使用 SQLite 数据库。但是控制台出现了新的错误。
TypeError: metatype is not a constructor.
TypeError: Cannot read property 'getHttpServer' of undefined.
好的,我通过在 Test.createTestingModule
的导入字段中使用 TypeOrm.forRoot()
解决了这个问题。以下是我的做法。
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [User],
logging: true,
synchronize: true,
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
userRepository = moduleFixture.get('UserRepository');
userService = new UserService(userRepository);
});
afterAll(async () => {
await app.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
});
对于那些正在寻找命中端点并断言响应主体的设置 e2e 测试的人,您可以这样做:
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
if (process.env.APPLICATION_ENV === 'test') {
return {
type: 'sqlite',
database: ':memory:',
entities: [Entity],
synchronize: true,
}
}
return {
// your default options
};
},
}),
]
})
在这个项目中,它使用了 NestJS 和 TypeORM。对于真正的 API 请求,CRUD 操作正在 MySQL(使用 AWS RDS)上运行。
现在我正在尝试使用 SQLite(内存中)来测试 API 结果。
我在单元测试中成功实现了这个,如下代码。
首先,下面是 create-memory-db.ts
,它 returns 连接到内存中的 SQLite 数据库。
type Entity = Function | string | EntitySchema<any>;
export async function createMemoryDB(entities: Entity[]) {
return createConnection({
type: 'sqlite',
database: ':memory:',
entities,
logging: false,
synchronize: true,
});
}
- 并且通过使用上面的导出函数,我成功地 运行 单元测试,如下所示。
describe('UserService Logic Test', () => {
let userService: UserService;
let connection: Connection;
let userRepository: Repository<User>;
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
// testing codes.
});
我正尝试在 e2e 测试中做同样的事情。我试过下面的代码。
// user.e2e-spec.ts
describe('UserController (e2e)', () => {
let userController: UserController;
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
userController = new UserController(userService);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [],
controllers: [UserController],
providers: [UserService],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
// await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
return request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect(HttpStatus.CREATED);
});
});
我无法创建 UserModule
,因为它没有带有 Connection
参数的构造函数。
代码本身没有编译错误,但是在执行e2e测试时得到如下结果。
Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index[0] is available in the RootTestModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current RootTestModule?
- If UserRepository is exported from a seperate @Module, is that module imported within RootTestModule?
@Module({
imports: [/* The module containing UserRepository */]
})
TypeError: Cannot read property 'getHttpServer' of undefined.
如有任何帮助,我们将不胜感激。谢谢:)
- 更新:尝试以下操作后出现新错误。
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [UserModule],
})
.overrideProvider(UserService)
.useClass(userService)
.compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', async () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
const result = await request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect({ status: HttpStatus.CREATED });
});
});
- 我检查了查询是否有效,并且能够看到它正在按我的需要使用 SQLite 数据库。但是控制台出现了新的错误。
TypeError: metatype is not a constructor.
TypeError: Cannot read property 'getHttpServer' of undefined.
好的,我通过在 Test.createTestingModule
的导入字段中使用 TypeOrm.forRoot()
解决了这个问题。以下是我的做法。
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test@test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [User],
logging: true,
synchronize: true,
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
userRepository = moduleFixture.get('UserRepository');
userService = new UserService(userRepository);
});
afterAll(async () => {
await app.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
});
对于那些正在寻找命中端点并断言响应主体的设置 e2e 测试的人,您可以这样做:
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
if (process.env.APPLICATION_ENV === 'test') {
return {
type: 'sqlite',
database: ':memory:',
entities: [Entity],
synchronize: true,
}
}
return {
// your default options
};
},
}),
]
})