NestJSでscope: Scope.Requestにしたサービスがあるとテストがコケる件


Scope.Requestを使うケース

こういうの
https://qiita.com/rdlabo/items/7c25b711fa8db99512a6

起きること

@Injectable({
  scope: Scope.Request
})
class AwesomeService {...}

こういうサービスを使っているクラスのテストがコケる。

  ● Site Controller › list site api › should return items

    TypeError: Cannot read property 'loadCurrentUser' of undefined

      35 |   })
      36 |   public async listSites(): Promise<SiteResponseItemDTO[]> {
    > 37 |     const { Username: username } = this.cognito.loadCurrentUser();
         |     

DIしたはずのクラスがundefinedになっており、メソッドが見つからないというエラーが出る。

実動作について

yarn startsls offlineなどで動かす分には問題がない。
あくまでテストだけ。

対応方法:Scopeをdefaultにする

要はScopeをテストの時だけDefaultにすればOKです。

対応1: テストコードで上書きする

createTestingModuleする時にscopeを上書きしてやると、undefinedにならなくなります。

      const mod = await Test.createTestingModule({
        providers: [{
          provide: LoginService,
          useClass: LoginService,
          scope: Scope.DEFAULT
        }],
      }).compile();

対応2: module.getmodule.resolveにする

対応1で動かない場合、テスト対象クラスを取得する処理をgetからresolveに変えると動く場合もあります。

      const mod = await Test.createTestingModule({
        controllers: [AdminController],
        providers: [{
          provide: LoginService,
          useClass: LoginService,
          scope: Scope.DEFAULT
        }],
      }).compile();

      // Before
      const controller = mod.get<AdminController>(AdminController)

      // After
      const controller = await mod.resolve<AdminController>(AdminController)

対応3: 定義側で変える

@Injectableする時に環境変数で変えてしまうという手もあります。

@Injectable({
  // @See https://github.com/nestjs/nest/issues/2049#issuecomment-559959575
  scope: process.env.NODE_ENV === 'test' ? Scope.DEFAULT : Scope.REQUEST,
})
class AwesomeService {...}

そのほか

Scope.Requestの場合、module.getではなくawait module.resolveを使う必要がある。

      const mod = await Test.createTestingModule({
        providers: [LoginService],
      }).compile();

      // Error
      // LoginService is marked as a scoped provider.
      // Request and transient-scoped providers can't be used in combination with "get()" method.
      // Please, use "resolve()" instead.
      const service = mod.get<LoginService>(LoginService);

      // うごく
      service = await mod.resolve<LoginService>(LoginService)

参考

結構遭遇している人が多いみたい。
https://github.com/nestjs/nest/issues/2049