[NestJS] @nestjs/bull に依存したクラスのテスト

16089 ワード

はじめに

最近 @nestjs/bull を使った非同期ジョブの実装をしていて、テストを書くのに少し手間取ったので解決策の共有になります。
ユニットテストと E2E テストでモックの内容が異なるのでそれぞれ記載しています。

また、テストコードは公式サンプルを元に作成しています。

ユニットテストの場合

getQueueToken() をモックすることでテストできます。

audio.controller.spec.ts
import { getQueueToken } from '@nestjs/bull';
import { Test, TestingModule } from '@nestjs/testing';
import { AudioController } from './audio.controller';

describe('AudioController', () => {
  let audioController: AudioController;

  const mockQueue = {
    add: jest.fn().mockName('AudioQueue.add')
  }

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      controllers: [AudioController],
      providers: [
        {
          provide: getQueueToken('audio'),
          useValue: mockQueue,
        }
      ],
    }).compile();

    audioController = moduleRef.get(AudioController);
  });

  describe('transcode', () => {
    it('should queue added', async () => {
      await audioController.transcode();

      expect(mockQueue.add).toBeCalled();
    });
  });
});

E2E テストの場合

E2E のように Module 単位でテストする場合は overrideProvider() を使うことでモックできます。
注意点として、Module 内に Consumer が含まれる場合は定義しているメソッドによって Queue に対して追加でメソッドのモックが必要です。(おそらく、Queue を遅延実行していない場合は即座に process が実行されてしまうため)

audio.e2e.spec.ts
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { AudioModule } from './audio.module';
import { getQueueToken } from '@nestjs/bull';

describe('e2e - Audio', () => {
  let app: INestApplication;

  const mockQueue = {
    add: jest.fn().mockName('AudioQueue.add'),
    // @Process() を定義している場合は必要
    process: jest.fn().mockName('AudioQueue.process'),
    /**
     * @OnXXX() を定義している場合は必要
     * @see https://docs.nestjs.com/techniques/queues#event-listeners
     */
    on: jest.fn().mockName('AudioQueue.add'),
  };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AudioModule],
    })
      .overrideProvider(getQueueToken('audio'))
      .useValue(mockQueue)
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it(`/POST transcode`, async () => {
    await request(app.getHttpServer())
      .post('/audio/transcode')
      .expect(201);

      expect(mockQueue.add).toBeCalled();
  });

  afterAll(async () => {
    await app.close();
  });
});

参考