#はじめに
最近バックエンドにts-node+TypeORMを用いて開発することが多いのですが、ts-node+TypeORM使用時のテスト環境構築の情報がなかなかないと感じたので、今回共有しようと思います。
##※前提条件
今回はテスト環境の構築のみを記述するので、ts-nodeでのRESTAPIサーバーorGraphQLサーバーの構築方法等は省略します。
またすでにtest
という名前のデータベースを作成済みで、TypeORMにて以下のようにデータベースのテーブルを構築済みとします。
@Entity('users')
export class User extends BaseEntity {
@Column()
username: string;
@Column()
email: string;
@Column()
password: string;
}
#環境構築
##1・ライブラリのインストール
###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
##2・TypeORM、データベースの設定
まず、テスト時に予め作成しておいたテスト用のDB(今回の場合test
と名付けたDB)に接続するようにTypeORMの設定を変更する必要があります。
公式ではcreateConnections
やConnectionManager
で複数の接続を管理する方法が紹介されていますが、試してみた所上手く動作しなかったので、今回はormconfig.js
と三項演算子でテスト時にテスト用のDBに接続する方法を紹介します。
また、今回はdotenv
で環境変数を読み込むのと三項演算子を使用するためにjson
形式ではなくjs
形式でファイルを設定します。
まずはormconfig.js
に以下のように設定を記述します。ormconfig.json
がすでに存在している場合は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
ファイルを参照するようにしています。
各人環境の違いがあると思うので、それぞれお好みで設定を変えて下さい。
##3・TypeORMSeedingの設定
次にTypeormSeedingの設定を行います。
TypeORMSeedingはTypeORM用の疑似データ(シードデータ)を作成するライブラリで、コードベースで簡単にシードデータを作成、管理することが可能になります。
TypeORMSeeding公式Githubページ
今回の例ではテスト用にUser
エンティティの疑似データを10件作成します。
まず初めに、TypeORMSeedingでは疑似データを作成するためにTypeORMのエンティティを基にした疑似データの構造体であるfactory
を定義する必要があります。
データはfaker
を使用してランダムなデータを生成しています。
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
を基に擬似データを作成するシーダーを作成します。
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コマンドを入力せずとも局所的に擬似データを挿入することが可能になります。
具体的な方法は後述します。
##4・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テストの並行実行と逐次実行をちゃんと理解する
##5・テストコードの記述
最後にテストコードを記述します。
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に関する情報を共有していきたいと思います。