typescriptnestjsvitestesbuildswc

NestJs dependency injection in constructor is undefined with class-based provider but works with token provider


I am trying to get NestJs dependency injection to work without needing a provider 'token'. For a simple example, this setup works:

@Injectable()
export class AClass {
  constructor() {
    console.log('AClass');
  }
}

@Injectable()
export class BClass {
  constructor(@Inject('A') readonly a: AClass) {
    console.log('BClass', a);
  }
}

@Module({
  providers: [
    { provide: 'A', useClass: AClass },
    { provide: 'B', useClass: BClass },
  ],
  exports: ['A', 'B'],
})
export class CModule {}

const moduleRef = await Test.createTestingModule({
  imports: [CModule],
})
.setLogger(
  new ConsoleLogger('logger', {
    logLevels: ['log', 'error', 'warn', 'debug', 'verbose'],
  }),
)
.compile();;

console.log('about to get BClass');
const x = moduleRef.get('B');
console.log('x:', x);

however, if I remove the tokens,

@Injectable()
export class BClass {
  constructor(readonly a: AClass) {
    console.log('BClass', a);
  }
}

@Module({
  providers: [AClass, BClass],
  exports: [AClass, BClass],
})
export class CModule {}

console.log('about to get BClass');
const x = moduleRef.get(BClass);
console.log('x:', x);

Then the AClass parameter to BClass's constructor is undefined.

It's also notable that the logging is a different for the broken second case. In this case I'm seeing:

[Nest] 76049  - 04/20/2024, 4:41:37 AM     LOG [InjectorLogger] Resolving dependency Reflector in the Reflector provider (alias)
stdout | unknown test
AClass
BClass undefined

[Nest] 76049  - 04/20/2024, 4:41:37 AM     LOG [InjectorLogger] Looking for Reflector in InternalCoreModule

where as in the working case, the instantiation of the classes is after all of the NestJs logging.

Update: Importantly the context here was in vitest ... this works with the Typescript compiler but not with esbuild as the latter doesn't support the emitDecoratorMetadata flag required by this form of dependency injection.


Solution

  • After more googling, I've realized I hit this issue: https://github.com/vitest-dev/vitest/issues/708

    Obviously, the context of using vitest here is important.

    The solution was to make vitest.config.ts have this plugin:

    import swc from 'unplugin-swc';
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      plugins: [swc.vite()],