はじめに
単体テストに引き続き、結合テストを実装しました。
実装したコードを確認したい方は以下よりご確認ください。
実装
ユーザの動作の流れをテストするために、各モジュールを組み合わせた結合テストを作成します。
これまでCookieSessionやValidationPipeの設定はmain.ts
内で行っていましたが、テストを実行するときにmain.ts
は実行されないので、これらの設定をAppModule
側に移してあげる必要があります。
まずValidationPipeについては、@Module()
のproviders
でAppService
の下に記述を加えます。
providers: [
AppService,
{
provide: APP_PIPE,
useValue: new ValidationPipe({
whitelist: true,
}),
},
],
cookieSessionについては、AppModule
クラス内に以下の記述を加えます。
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(
cookieSession({
keys: ['key1'],
}),
);
}
}
以下のテストケースで新規登録に関する結合テストを作成すると以下のようになります。
-
/auth/signup
にPOSTリクエストを投げて(新規登録を行って)、登録したユーザのidとemailがレスポンスとして返ってくるかどうか - 新規登録後に
/auth/whoami
にGETリクエストを投げて、ログインユーザのemailがレスポンスとして返ってくるかどうか
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('Authentication System', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('handles a signup request', () => {
const email = 'asdlkjqdaa@akl.com';
return request(app.getHttpServer())
.post('/auth/signup')
.send({ email, password: 'alsdk' })
.expect(201)
.then((res) => {
const { id, email } = res.body;
expect(id).toBeDefined();
expect(email).toEqual(email);
});
});
it('signup as a new user then get the currency logged in user', async () => {
const email = 'asdf@asdf.com';
const res = await request(app.getHttpServer())
.post('/auth/signup')
.send({ email, password: 'asdf' })
.expect(201);
const cookie = res.get('Set-Cookie');
const { body } = await request(app.getHttpServer())
.get('/auth/whoami')
.set('Cookie', cookie)
.expect(200);
expect(body.email).toEqual(email);
});
});
無事にテストが完成したようにみえますが、改善すべき点がいくつか存在します。
テストデータ用のDBを用意する
現在の設定だと、テスト環境でもdb.sqlite
を使用しているため、テストデータと本番データが混ざってしまいます。
そのため、テスト時には使用するDBを切り替えるような設定が必要となります。
まずは.env.development
と.env.test
ファイルを作成し、DB_NAME
をそれぞれ設定します。
DB_NAME=db.sqlite
DB_NAME=test.sqlite
これらの環境変数を取得する際にConfigModule
およびConfigService
を使用するため、@nest/config
をインストールします。
npm install @nestjs/config
ConfigModule.forRoot()
でenvのファイル名を指定します。
環境によって参照するファイルを切り替えるために、process.env.NODE_ENV
を使用しています。
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
type: 'sqlite',
database: config.get<string>('DB_NAME'),
entities: [User, Report],
synchronize: true,
};
},
}),
npmスクリプトを実行する際に任意の環境変数を設定するパッケージcross-env
をインストールします。
npm install cross-env
cross-env NODE_ENV=test
などをスクリプトに加えてあげることで環境変数が設定されます。
"start": "cross-env NODE_ENV=development nest start",
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "cross-env NODE_ENV=test jest --watch --maxWorkers=1",
"test:cov": "cross-env NODE_ENV=test jest --coverage",
"test:debug": "cross-env NODE_ENV=test node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "cross-env NODE_ENV=test jest --config ./test/jest-e2e.json"
試しにnpm run test:e2e
でテストを実行してみると、以下のエラーがでます。
このエラーは記述した2つのテストで各々がtest.sqlite
に接続しようとしているために起こります。
test:e2e
のスクリプトに--maxWorkers=1
を加えることで、接続がバッティングしないようになります。
"test:e2e": "cross-env NODE_ENV=test jest --config ./test/jest-e2e.json --maxWorkers=1"
テストごとにDBにデータがたまらないようにする
新規登録のテストを複数回行ったとき、1度目にユーザデータが作成されるため、2度目以降は失敗します。
テストの回数によって結果が変わってしまうのはよくないので、テストが終わったらDBを元の状態に戻すようにします。
setup.ts
を作成し、テスト前global.beforeEach()
でDBを削除する処理を記述します。
(node v14.14.0以降であればawait rm(join(__dirname, '..', 'test.sqlite'));
)
そして、テスト後global.afterEach()
にはDBへの接続を切断する処理を記述します。
import { join } from 'path';
import { promises } from 'fs';
import { getConnection } from 'typeorm';
global.beforeEach(async () => {
try {
await promises.unlink(join(__dirname, '..', 'test.sqlite'));
} catch (err) {}
});
global.afterEach(async () => {
const conn = getConnection();
await conn.close();
});
jest-e2e.json
に"setupFilesAfterEnv": ["<rootDir>/setup.ts"]
を追記すれば設定完了です。
参考資料