NestJS(Fastify)+ JestでE2Eテストを実装する

NestJS+ Jestで最低限のE2Eテストを実装する(HTTPアダプターとしてFastifyを使用している前提)。

テスト対象

下記のようなThis is a sample.という文字列を返すだけのSampleモジュールをテストする。

sample/sample.module.ts
import { Module } from '@nestjs/common';
import { GetSampleUsecase } from './usecases/get-sample.usecase';
import { SampleController } from './sample.controller';
@Module({
controllers: [SampleController],
providers: [GetSampleUsecase],
})
export class SampleModule {}
sample/sample.controller.ts
import { Controller, Get } from '@nestjs/common';
import { GetSampleUsecase } from './usecases/get-sample.usecase';
@Controller('sample')
export class SampleController {
constructor(private readonly getSampleUsecase: GetSampleUsecase) {}
@Get()
getSample(): string {
return this.getSampleUsecase.handle();
}
}
sample/usecases/get-sample.usecase.ts
export class GetSampleUsecase {
handle(): string {
return 'This is a sample.';
}
}

Jestの設定

/
├── src
└── test
├── unit
└── e2e
└── sample

NestJSではデフォルトでユニットテストはsrc下に、E2Eテストはtest下に置くようになっているが、個人的な好みとしてどちらもtest下に配置する。

package.json
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "test",
"testEnvironment": "node",
"testRegex": ".*\\..*spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
}

テストコード

1. 下準備

e2e/setup-e2e.ts
import { Test, type TestingModule } from '@nestjs/testing';
import {
FastifyAdapter,
type NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { mainConfig } from '@src/main.config';
import { EnvModule } from '@src/config/env/env.module';
import { DatabaseModule } from '@src/config/database/database.module';
import type { Type } from '@nestjs/common/interfaces/type.interface';
import type { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface';
import type { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface';
type Output = {
module: TestingModule;
app: NestFastifyApplication;
};
export async function setupE2e(
imports: (
| Type<any>
| DynamicModule
| Promise<DynamicModule>
| ForwardReference
)[],
): Promise<Output> {
const module = await Test.createTestingModule({
imports,
}).compile();
const app = module.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
await app.init();
await app.getHttpAdapter().getInstance().ready();
return { module, app };
}

E2Eテストで共通となる下準備の処理を実装する。

ここで返されるmoduleappを各E2Eテストで使用し、そのモジュールのプロバイダーを取得したりリクエストを投げたりする。

const module = await Test.createTestingModule({
imports,
imports: [...imports, EnvModule, DatabaseModule],
}).compile();

もしAppModuleなどで環境変数やDB周りのモジュールをimportしている場合は、上記のようにimportsに追加する。

const app = module.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
app.useGlobalPipes(new ValidationPipe());
await app.init();
await app.getHttpAdapter().getInstance().ready();

もしグローバルなPipeやGuardをmain.ts内でバインドしている場合は、上記のようにここで設定しておく。

2. E2Eテストの実装

e2e/sample/sample.e2e-spec.ts
import { setupE2e } from '../setup-e2e';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import { HttpStatus } from '@nestjs/common';
import { SampleModule } from '@src/sample/sample.module';
describe('sample(E2E)', () => {
let app: NestFastifyApplication;
beforeAll(async () => {
const { module, app: initializedApp } = await setupE2e([SampleModule]);
app = initializedApp;
});
afterAll(async () => {
await app.close();
});
describe('/sample(GET)', () => {
it('GETリクエスト_サンプルのテキストが返る', async () => {
// run
const { statusCode, body } = await app.inject({
method: 'GET',
path: 'sample',
});
// assert
expect(statusCode).toBe(HttpStatus.OK);
expect(body).toContain('This is a sample.');
});
});
});

あとはそのappを使用しリクエストを投げて検証すれば実装完了。

Terminal window
npm run test e2e/sample/sample.e2e-spec.ts
PASS test/e2e/sample/sample.e2e-spec.ts (5.784 s)
sample(E2E)
/sample(GET)
✓ GETリクエスト_サンプルのテキストが返る (17 ms)

テストを実行し無事成功も確認できた。

let app: NestFastifyApplication;
let sampleRepository: SampleRepository;
beforeAll(async () => {
const { module, app: initializedApp } = await setupE2e([SampleModule]);
app = initializedApp;
sampleRepository = module.get<SampleRepository>(SampleRepository);
});

もしそのモジュールのプロバイダーを使用したい場合はmoduleからget()すればよい。

1

参考
  1. Testing | NestJS - A progressive Node.js framework