やりたいこと
nestjs、typeORMの環境でtypeORMのカスタムリポジトリのテストを書きたい。
テストは以下のような感じで実行したい。
- テストデータを用意し、実際に処理を動かしてテストしたい(カスタムリポジトリの関数をmockするテストはしたくない。)
- テストの度にテストデータはリセットされるようにしたい(各テストごとにテストデータを用意する。テストデータの共有はしたくない)
これの参考になるようなコードがあまりネットに見つからなかったので残しておく。
コード
テスト対象のentity、repository
このentityのテストを書いていく。
- 外部キーが貼られている
- ユニークキーが貼られている
import { Entity, Column, PrimaryGeneratedColumn, JoinColumn, ManyToOne, Unique } from 'typeorm';
import { ItemSku } from './itemSku';
@Entity('user_deliverd_sku_relations')
@Unique(['email', 'item_sku_id'])
export class UserDeliverdSkuRelation {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
})
email: string;
@Column({
nullable: false,
})
item_sku_id: number;
@ManyToOne((type) => ItemSku)
@JoinColumn({ name: 'item_sku_id' })
item_sku: ItemSku;
@Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
})
created_at: Date;
@Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
})
updated_at: Date;
}
import { Repository, EntityRepository } from 'typeorm';
import { UserDeliverdSkuRelation } from '../../entity/userDeliverdSkuRelation';
@EntityRepository(UserDeliverdSkuRelation)
export class DeliveredItemSkuRepository extends Repository<UserDeliverdSkuRelation> {
findByEmail(email: string) {
return this.find({
where: {
email,
},
});
}
}
リポジトリのテストコード
できたテストコードがこれ
import { createConnection, getConnection } from 'typeorm';
import { UserDeliverdSkuRelation } from '../../entity/userDeliverdSkuRelation';
import { ItemSku } from '../../entity/itemSku';
import { DeliveredItemSkuService } from '../../service/deliveredItemSku/service';
import { DeliveredItemSkuRepository } from '../../repository/deliveredItemSku';
import { ItemSkuRepository } from '../../repository/itemSku';
import dbConfig from '../../../src/config/database';
describe('DeliveredItemSkuRepository', () => {
let deliveredItemSkuService: DeliveredItemSkuService;
let deliveredItemSkuRepository: DeliveredItemSkuRepository;
let itemSkuRepository: ItemSkuRepository;
beforeEach(async () => {
const connection = await createConnection({
type: dbConfig.type,
host: dbConfig.host,
port: parseInt(dbConfig.port, 10),
username: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
entities: [UserDeliverdSkuRelation, ItemSku],
synchronize: true,
dropSchema: true,
logging: false,
name: dbConfig.connectionName,
});
deliveredItemSkuRepository = connection.getCustomRepository(DeliveredItemSkuRepository);
deliveredItemSkuService = new DeliveredItemSkuService(deliveredItemSkuRepository, connection);
itemSkuRepository = connection.getCustomRepository(ItemSkuRepository);
});
afterEach(async () => {
await deliveredItemSkuRepository.delete({});
await itemSkuRepository.delete({});
await getConnection(dbConfig.connectionName).close();
});
describe('定義されている', () => {
it('リポジトリが定義されている', () => {
expect(deliveredItemSkuRepository).toBeInstanceOf(DeliveredItemSkuRepository);
});
});
describe('findByEmail', () => {
it('指定したemailのUserDeliverdSkuRelationエンティティの配列が返ってくる', async () => {
const testData = [
{
id: 1,
email: 'hoge@example.com',
item_sku_id: 1,
},
{
id: 1,
email: 'bar@example.com',
item_sku_id: 1,
},
] as UserDeliverdSkuRelation[];
await itemSkuRepository.insert({
id: 1,
});
await deliveredItemSkuRepository.insert(testData);
const res = await deliveredItemSkuRepository.findByEmail('hoge@example.com');
expect(res.length).toEqual(1);
expect(res[0]).toMatchObject({
id: 1,
email: 'hoge@example.com',
item_sku_id: 1,
});
});
});
describe('insert', () => {
describe('できるパターン', () => {
it('insertしたレコードが確認できる', async () => {
const testData = {
id: 1,
email: 'hoge@example.com',
item_sku_id: 1,
} as UserDeliverdSkuRelation;
await itemSkuRepository.insert({
id: 1,
});
await deliveredItemSkuRepository.insert(testData);
expect(await deliveredItemSkuService.findAll()).toEqual([testData]);
});
});
describe('できないパターン', () => {
it('外部キー制約でinsertに失敗する。(ひもづくitem_skusがないため)', async () => {
const testData = {
id: 1,
email: 'hoge@example.com',
item_sku_id: 1,
} as UserDeliverdSkuRelation;
await expect(deliveredItemSkuRepository.insert(testData)).rejects.toThrow(
/insert or update on table "user_deliverd_sku_relations" violates foreign key*/,
);
});
it('ユニークキー制約でinsertに失敗する。(emailとitem_sku_idでユニーク)', async () => {
await itemSkuRepository.insert({
id: 1,
});
const testData1 = {
id: 1,
email: 'hoge@example.com',
item_sku_id: 1,
} as UserDeliverdSkuRelation;
await deliveredItemSkuRepository.insert(testData1);
const testData2 = {
id: 2,
email: 'hoge@example.com',
item_sku_id: 1,
} as UserDeliverdSkuRelation;
await expect(deliveredItemSkuRepository.insert(testData2)).rejects.toThrow(
/duplicate key value violates unique constraint*/,
);
});
});
});
});
ポイント
各テスト実行前
beforeEachで以下を行なっている。
beforeEach(async () => {
const connection = await createConnection({
type: dbConfig.type,
host: dbConfig.host,
port: parseInt(dbConfig.port, 10),
username: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
entities: [UserDeliverdSkuRelation, ItemSku],
synchronize: true,
dropSchema: true,
logging: false,
name: dbConfig.connectionName,
});
これによって、以下がテストの度に実行されるようになる。
DBとの接続
createConnectionで接続し直すテーブルのdrop
dropSchema: true
によって接続が確立される度にテーブルをdropする。対象はentitiesに記載されたentity。マイグレーション
synchronize: true
によってマイグレーションを流し直す。
これでテストのたびにまっさらなテーブル(UserDeliverdSkuRelation, ItemSku)が用意されるようになる。
各テスト実行後
afterEachでテストに関わるentityのデータを削除をする。
beforeEachの処理によって、テスト実行前のテストデータ削除はできているが、テスト実行後にもテストデータを削除しておくことで他のテストへ影響を与えないようにする。
afterEach(async () => {
await deliveredItemSkuRepository.delete({});
await itemSkuRepository.delete({});
await getConnection(dbConfig.connectionName).close();
});
これで当初やりたかった テストの度にテストデータはリセットされる
ができてると思う。