ts-jest + TypeORM + TypeORMSeedingでts-nodeテスト環境を構築する方法

#はじめに
最近バックエンドにts-node+TypeORMを用いて開発することが多いのですが、ts-node+TypeORM使用時のテスト環境構築の情報がなかなかないと感じたので、今回共有しようと思います。

##※前提条件
今回はテスト環境の構築のみを記述するので、ts-nodeでのRESTAPIサーバーorGraphQLサーバーの構築方法等は省略します。
またすでに`test`という名前のデータベースを作成済みで、TypeORMにて以下のようにデータベースのテーブルを構築済みとします。

````typescript:Entities/User.ts
@Entity('users')
export class User extends BaseEntity {

    @Column()
    username: string;
    
    @Column()
    email: string;
    
    @Column()
    password: string;

}
````

#環境構築
##１・ライブラリのインストール
###Typescript

````
yarn add (or npm i) -D Typescript @types/node ts-node
````

###TypeORM

````
yarn add (or npm i) typeorm typeorm-seeding
````

###Jest
````
yarn add (or npm i) -D jest ts-jest @types/jest
````

###その他
````
yarn add (or npm i) faker

yarn add (or npm i) -D @types/faker

yarn add (or npm i) dotenv
````


##２・TypeORM、データベースの設定

まず、テスト時に予め作成しておいたテスト用のDB(今回の場合`test`と名付けたDB)に接続するようにTypeORMの設定を変更する必要があります。
公式では`createConnections`や`ConnectionManager`で複数の接続を管理する方法が紹介されていますが、試してみた所上手く動作しなかったので、今回は`ormconfig.js`と三項演算子でテスト時にテスト用のDBに接続する方法を紹介します。
また、今回は`dotenv`で環境変数を読み込むのと三項演算子を使用するために`json`形式ではなく`js`形式でファイルを設定します。

まずは`ormconfig.js`に以下のように設定を記述します。`ormconfig.json`がすでに存在している場合は`ormconfig.js`に名称を変えて下さい。

````javascript:ormconfig.js
module.exports = {
	type: process.env.DATABASE_TYPE_DEVELOPMENT,
	host: process.env.DATABASE_HOST_DEVELOPMENT,
	port: process.env.DATABASE_PORT_DEVELOPMENT,
	username: process.env.DATABASE_USERNAME_DEVELOPMENT,
	password: process.env.DATABASE_PASSWORD_DEVELOPMENT,
	database:
		process.env.NODE_ENV === 'test'
			? process.env.DATABASE_NAME_TEST
			: process.env.DATABASE_NAME_DEVELOPMENT,
	logging: false,
	synchronize: process.env.NODE_ENV === 'test' ? true : false,
	dropSchema: process.env.NODE_ENV === 'test' ? true : false,
	entities:
		process.env.NODE_ENV === 'test'
			? ['src/entities/**/*.ts']
			: ['dist/entities/**/*.js'],
	migrations: ['dist/migration/**/*.js'],
	subscribers: ['dist/subscriber/**/*.js'],
	cli: {
		entitiesDir: 'src/entities',
		migrationsDir: 'src/migration',
		subscribersDir: 'src/subscriber',
	},
};

````

上記の設定では`NODE_ENV === 'test'`時に`database`はテスト用のDBに接続し、`synchronize`と`dropSchema`は`true`に(`syncronize`をtrueにすると`migration run`コマンドを入力しなくても自動的にスキーマの変更がDBに反映され、`dropSchema`をtrueにすると接続時にDBテーブル内のデータを全て消去してくれます)、`entities`はテスト時は`.ts`ファイルを参照するようにしています。
各人環境の違いがあると思うので、それぞれお好みで設定を変えて下さい。


##３・TypeORMSeedingの設定

次にTypeormSeedingの設定を行います。
TypeORMSeedingはTypeORM用の疑似データ（シードデータ）を作成するライブラリで、コードベースで簡単にシードデータを作成、管理することが可能になります。
[TypeORMSeeding公式Githubページ](https://github.com/w3tecch/typeorm-seeding)
今回の例ではテスト用に`User`エンティティの疑似データを１０件作成します。

まず初めに、TypeORMSeedingでは疑似データを作成するためにTypeORMのエンティティを基にした疑似データの構造体である`factory`を定義する必要があります。
データは`faker`を使用してランダムなデータを生成しています。

````typescript:user.factory.ts
import { define } from 'typeorm-seeding';
import * as Faker from 'faker/locale/ja';

import { User } from './entities/User';

define(User, (faker: typeof Faker): User => {
	const user = new User();
　　　　　//↓ライブラリ「faker」でランダムなデータを作成する
        user.username = faker.internet.username();
        user.email = faker.internet.email();
        user.password = faker.internet.password();

	return user;
});

export default User;

````

次にこの`User factory`を基に擬似データを作成するシーダーを作成します。

````typescript:user.seed.ts
import { Factory, Seeder } from 'typeorm-seeding';

import User from '../factories/user.factory';

export class CreateUsersSeed implements Seeder {
	public async run(factory: Factory) {
		await factory(User)().createMany(20);
	}
}

````

これで擬似データを作成する準備が整いました。
作成は`yarn seed:run`のように起動スクリプトを記述してcliコマンドで作成する方法もあるのですが今回作成する疑似データはテスト環境用のため、cliコマンドを入力して疑似データを最初に一度作るだけではあるテスト結果が他のテスト結果にも影響を及ぼす可能性があるので、今回は
(テスト毎に)
**DB接続＆DB内のデータをクリア**
       ↓
**疑似データ作成**
(全てのテスト終了後)
**DB内のデータをクリア＆DB接続終了**
というプロセスをテスト毎に行い、それぞれのテスト結果が他のテストに影響を及ぼさないように設定したいと思います。`TypeORMSeeding`にはテスト用に使用できる`useSeeding()`や`useRefreshDatabase()`などの便利なメソッドが用意されており、これらを使用することでcliコマンドを入力せずとも局所的に擬似データを挿入することが可能になります。
具体的な方法は後述します。

##４・ts-jestの設定

Typescriptで開発をしている場合にjestでテストを記述する際は`Babel`か`ts-jest`を使用してテストコードをトランスパイルする必要が出てきます。
今回は`ts-jest`を使用して環境構築をしていきます。
必要なライブラリをインストールした後に
````
yarn ts-jest config:init
````

このコマンドを入力すればプロジェクト内に`jest.config.js`が作成されます。
次に`package.json`にテスト起動のnpmスクリプトを記述します。

````
"scripts": {
    "test": "NODE_ENV=test jest --runInBand",
}
````

ポイントはjestに`--runInBand`オプションを渡している点です。
jestはデフォルトでは**複数のテストファイルを並行実行する仕様**になっているため、もしプロジェクトに複数のテストファイルを用意していてかつその全てのファイルが同じDBに接続するような状況ではエラーを起こしてしまいます。
今回の例ではテストファイルを一つしか用意しておりませんが、今後ファイルが増えて複数のテストファイルがテスト用のDBに接続するような状況になった時には`--runInBand`オプションをわたして逐次実行（ファイルを一つずつ順に実行していく処理）に切り替えたほうが良いです。
↓参考資料
[Jestテストの並行実行と逐次実行をちゃんと理解する](https://qiita.com/noriaki/items/5d800ea1813c465a0a11)


##５・テストコードの記述

最後にテストコードを記述します。

````typescript:user.test.ts

import {
	runSeeder,
	tearDownDatabase,
	useRefreshDatabase,
	useSeeding,
} from 'typeorm-seeding';


import { CreateUsersSeed } from './seeds/user.seed';


beforeEach(async () => {
　　　　　//DBに接続＆内部のデータをクリア
	await useRefreshDatabase();
        //プロジェクト内のfactoryをロードする
	await useSeeding();
        //シーダーを実行する
	await runSeeder(CreateUsersSeed);
});

afterAll(async () => {
　      //DBとの接続を終了する
	await tearDownDatabase();
});


describe('test', () => {
   //テストコードを記述
})

````

このようになります。
jestを実行する際に`NODE_ENV=test`を指定しているため、`useRefreshDatabase`でDBに接続する時には`ormconfig.js`の設定どおりテスト用のDBに接続されるようになっています。
あとはテストコードを記述するだけです！

#まとめ

テスト環境の構築は機能をモックすることや疑似データを用意することがなかなか困難ですが、Jest+TypeORM+TypeORMSeedingを使用すれば多少は楽にテスト環境が構築できるのではないかと思います。
今後もTypeORMやJestに関する情報を共有していきたいと思います。