Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a nestjs service by passing in a ConfigService with custom values?

I've created a service, and the module for it looks like this:

launchdarkly.module.ts

@Module({
  providers: [LaunchdarklyService],
  exports: [LaunchdarklyService],
  imports: [ConfigService],
})
export class LaunchdarklyModule {}

(this service/module is to let the application use LaunchDarkly feature-flagging)

I'm happy to show the service-implementation if you'd like, but to keep this question shorter I skipped it. The important point is that this service imports the ConfigService (which it uses to grab the LaunchDarkly SDK key).

But how can I test the Launchdarkly service? It reads a key from ConfigService so I want to write tests where ConfigService has various values, but after hours of trying I can't figure out how to configure ConfigService in a test.

Here's the test:

launchdarkly.service.spec.ts

describe('LaunchdarklyService', () => {
  let service: LaunchdarklyService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [LaunchdarklyService],
      imports: [ConfigModule],
    }).compile();

    service = module.get<LaunchdarklyService>(LaunchdarklyService);
  });

  it("should not create a client if there's no key", async () => {
    // somehow I need ConfigService to have key FOO=undefined for this test
    expect(service.client).toBeUndefined();
  });

  it("should create a client if an SDK key is specified", async () => {
    // For this test ConfigService needs to specify FOO=123
    expect(service.client).toBeDefined();
  });
})

I'm open for any non-hacky suggestions, I just want to feature-flag my application!

like image 837
Jon Lauridsen Avatar asked Jan 08 '21 22:01

Jon Lauridsen


2 Answers

Assuming the LaunchdarklyService needs the ConfigService and that is injected into the constructor, you can provide a mock variation of the ConfigService by using a Custom Provider to give back the custom credentials you need. For example, a mock for your test could look like

describe('LaunchdarklyService', () => {
  let service: LaunchdarklyService;
  let config: ConfigService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [LaunchdarklyService, {
        provide: ConfigService,
        useValue: {
          get: jest.fn((key: string) => {
            // this is being super extra, in the case that you need multiple keys with the `get` method
            if (key === 'FOO') {
              return 123;
            }
            return null;
          })
        }
      ],
    }).compile();

    service = module.get<LaunchdarklyService>(LaunchdarklyService);
    config = module.get<ConfigService>(ConfigService);
  });

  it("should not create a client if there's no key", async () => {
    // somehow I need ConfigService to have key FOO=undefined for this test
    // we can use jest spies to change the return value of a method
    jest.spyOn(config, 'get').mockReturnedValueOnce(undefined);
    expect(service.client).toBeUndefined();
  });

  it("should create a client if an SDK key is specified", async () => {
    // For this test ConfigService needs to specify FOO=123
    // the pre-configured mock takes care of this case
    expect(service.client).toBeDefined();
  });
})
like image 175
Jay McDoniel Avatar answered Oct 10 '22 23:10

Jay McDoniel


Instead of providing ConfigService you need to import the ConfigModule with mocked data. As an example

imports: [CommonModule,ConfigModule.forRoot({
                ignoreEnvVars: true,
                ignoreEnvFile: true,
                load: [() => ({ IntersectionOptions: { number_of_decimal_places: '3' }})],
            })],
like image 1
Moeid Heidari Avatar answered Oct 10 '22 23:10

Moeid Heidari