nestjsnestjs-testing

How to override provider for tests in NestJs and not create transitive dependencies that are not needed?


I am writing end-to-end tests in NestJs and using the "overrideProvider" function to setup test versions of various services (e.g. services that require database connections). I noticed though that even when I do this, the original implementation that injects the real database is still instantiated.

Is there a way to tell Nest to not create transitive dependencies that are overridden?

For example, I have a test that starts like:

...

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [ServiceModule],
    })
    // Works if I uncomment these lines:
    // .overrideProvider('Database')
    // .useValue(new TestDatabase())
    .overrideProvider('ServiceUsingDatabase')
    .useValue(new TestService())
    .compile();

...

Where the module setup is like:

import { Inject, Injectable, Module } from '@nestjs/common';

interface Database {} 

@Injectable()
class ProductionDatabase implements Database {
  constructor() {
    throw('Cannot create a production database.');
  }
}

@Injectable()
export class TestDatabase implements Database {
  constructor() {
    console.log('Creating the test database.');
  }
}

@Module({
  providers: [
    {
      provide: 'Database',
      useClass: ProductionDatabase

    }
  ],
  exports: ['Database']
})
class DatabaseModule {}


interface Service {}

@Injectable()
class ProductionService implements Service {
  constructor(@Inject('Database') private readonly database: Database) {}
}

@Injectable()
export class TestService implements Service {
  // Test implementation of the service does not Inject anything.
}

@Module({
  imports: [DatabaseModule],
  providers: [
    {
      provide: 'ServiceUsingDatabase',
      useClass: ProductionService
    }
  ],
})
export class ServiceModule {}

But, the DI system is still seeming to try and instantiate ProductionDatabase. If I explicitly override the provider for the 'Database' it works, but I'd like to avoid having to explicitly list all transitive dependencies as such.


Solution

  • I ended up deciding to make a "Test" Module for every Module e.g.:

    import { Inject, Injectable, Module } from '@nestjs/common';
    
    interface Database {} 
    
    @Injectable()
    class ProductionDatabase implements Database {
    }
    
    @Injectable()
    export class TestDatabase implements Database {
    }
    
    @Module({
      providers: [
        {
          provide: 'Database',
          useClass: ProductionDatabase
    
        }
      ],
      exports: ['Database']
    })
    class DatabaseModule {}
    
    @Module({
      providers: [
        {
          provide: 'Database',
          useClass: TestDatabase
    
        }
      ],
      exports: ['Database']
    })
    class TestDatabaseModule {}
    
    
    interface Service {}
    
    @Injectable()
    class ProductionService implements Service {
      constructor(@Inject('Database') private readonly database: Database) {}
    }
    
    @Injectable()
    export class TestService implements Service {
    }
    
    @Module({
      imports: [DatabaseModule],
      providers: [
        {
          provide: 'ServiceUsingDatabase',
          useClass: ProductionService
        }
      ],
    })
    export class ServiceModule {}
    
    
    @Module({
      providers: [
        {
          provide: 'ServiceUsingDatabase',
          useClass: TestService
        }
      ],
    })
    export class TestServiceModule {}
    

    etc... Though it turned out after some refactorings that the "Test" module wasn't needed as some Modules became pure business logic.