테스트 환경을 위해 완전히 새로운 데이터베이스를 생성하고 테스트 이후 삭제하기 때문에, 데이터베이스는 항상 모든 데이터가 없는 상태로 존재하게 됩니다.
하지만 서비스 운영을 위해 DB에서 관리하는 상수값 혹은 여러 설정 데이터들이 존재해야 하는 경우가 있습니다. 그러한 경우에는 서비스가 정상적으로 동작하지 않기 때문에 테스트 코드 또한 정상적으로 동작하지 않습니다.
그래서 테스트를 시작하기 전에 데이터를 미리 데이터베이스에 삽입해야 합니다.
typeorm 에는 자체적으로 seeder를 지원하고 있지 않습니다. 하지만 공식적으로 소개하고 있는 익스텐션에 seeder를 사용할 수 있게 되어 있습니다. 해당 익스텐션을 이용해 보도록 하겠습니다.
yarn add typeorm-extension
Yarn 혹은 Npm과 같은 패키지 매니저로 typeorm-extension
을 설치합니다. 이 글에서는 Yarn을 사용하는 방법으로 설명합니다.
그리고 Seeder를 사용하기 Command line을 package.json의 스크립트를 추가합니다.
{
...
"seed": "ts-node -r tsconfig-paths/register $(yarn bin typeorm-extension) -d src/libs/typeorm/data-source.ts seed:run",
...
}
이제 yarn run seed 를 터미널에 입력하면 DB에 데이터가 추가될 것입니다.
상수값을 정의하여 사용하기 위해 우선 Platform 이라는 엔티티를 추가로 선언했습니다. 해당 Entity는 블록체인 네트워크를 의미하게 됩니다.
// platform.entity.ts
@Entity()
export class Platform {
@PrimaryGeneratedColumn({ type: 'bigint' })
idx: string;
@Column({ type: 'varchar', length: 255, nullable: false, comment: '플랫폼 이름' })
name: string;
@Column({ type: 'varchar', length: 255, nullable: false, comment: '플랫폼 심볼' })
symbol: string;
@Column({ type: 'boolean', nullable: false, comment: 'Utxo 사용 여부' })
useUtxo: boolean;
@Column({ type: 'boolean', nullable: false, comment: '수수료 주소 사용 여부' })
useFeeAddress: boolean;
@CreateDateColumn()
createdDate: Date;
@UpdateDateColumn()
updatedDate: Date;
}
이름과 심볼, 플랫폼 별 특성을 나타내기 위한 속성들이 정의되어 있습니다. 이 값은 한번 저장되면 변경될 일이 거의 없는 상수값으로 사용되지만, 여러 데이터를 저장하기 위해 참조하는 테이블로 사용될 것입니다.
이 테이블에 테스트를 위한 데이터를 삽입하는 Seeder 파일을 선언해 보겠습니다.
// platform.seeder.ts
export default class PlatformSeeder implements Seeder {
public async run(dataSource: DataSource): Promise<any> {
const repository = dataSource.getRepository(Platform);
await repository.insert([
{
name: '비트코인',
symbol: 'BTC',
useUtxo: true,
useFeeAddress: false,
},
{
name: '이더리움',
symbol: 'ETH',
useUtxo: false,
useFeeAddress: true,
},
]);
}
}
단순하게 dataSource에서 레포지토리를 가져와 해당 레포지토리에 데이터를 저장하는 코드로 되어 있습니다.
테스트를 위해 비트코인과 이더리움을 삽입하는 Seeder 파일을 작성했습니다. 이제 테스트 시작 전에 이 Seeder 파일을 실행하기만 하면, 테스트를 위한 데이터가 자동으로 삽입되어 테스트를 진행할 것입니다.
TypeORM은 DataSource라는 단위를 통해 데이터베이스의 커넥션을 관리합니다. Seeder도 TypeORM을 이용해 동작하기 때문에 Seeder가 동작할 DataSource를 선언해 주어야 합니다.
// data-source.ts
import { ConfigService } from '@nestjs/config';
import { config } from 'dotenv';
import { DataSource, DataSourceOptions } from 'typeorm';
import { MysqlTestConfig } from '../../common/config/mysql.config';
import { SeederOptions } from 'typeorm-extension';
config();
const configService = new ConfigService({ app: { env: 'test' }, database: { mysqlPoolSize: 10 } });
const mysqlConfig = new MysqlTestConfig(configService);
const options: DataSourceOptions & SeederOptions = {
...(mysqlConfig.createTypeOrmOptions() as DataSourceOptions),
seeds: ['src/common/database/seeders/**/*{.ts,.js}'],
factories: ['src/common/database/factories/**/*{.ts,.js}'],
entities: ['src/common/entities/**/*{.ts,.js}'],
};
export default new DataSource(options);
기존에 테스트에서 사용하는 MySQL Config을 선언해 주었는데, 그 값을 재사용하여 유지 보수에 어려움을 하나 줄여주도록 합니다.
그리고 seeder와, factory, entity 경로를 선언해 줍니다. seeder와 factory는 경로를 설정하지 않은 경우에 기본적으로 설정된 경로에서 seeder와 factory 파일을 찾아 실행합니다. 기본 경로는 src/database/{seeds,factory}/**/*{.ts,.js}
입니다. 원하는 경로로 seeder와 factory를 설정해 줍니다.
...
describe('/platforms', () => {
it('/platforms (GET)', async () => {
return request(app.getHttpServer())
.get('/platforms')
.expect(200)
.expect((res) => {
expect(res.body).toHaveLength(2);
expect(res.body).toMatchObject([
{ name: '비트코인', symbol: 'BTC' },
{ name: '이더리움', symbol: 'ETH' },
]);
});
});
});
...
테스트 코드를 작성합니다. 모든 플랫폼 목록을 조회하는 API가 있고 해당 API를 호출하면 Platform 테이블에 존재하는 플랫폼 목록을 출력합니다. 정상적인 경우에 Seed에 선언한 대로 2개의 플랫폼이 응답되고 그 플랫폼은 각각 비트코인 이더리움을 응답할 것입니다.
테스트용 도커를 실행하고 나면, 어떤 데이터도 없기 때문에 회원가입 테스트는 성공하고, 플랫폼 조회 API 테스트는 아무 데이터가 응답되지 않아 실패합니다.
Seed를 추가하는 스크립트를 실행하고 나면, 플랫폼이 데이터베이스에 추가되어 테스트가 정상적으로 통과합니다.