Steven Scott
Steven Scott

Reputation: 11260

NestJS Testing ConfigService works

I am writing an application to handle requests and return predefined responses to allow testing of external REST endpoints by software that cannot have internal tests written into it currently. Therefore, my code uses the Nest JS framework to handle the routes, and then extracts values and returns data. The data returned is stored in external files.

To handle constant changes and different team usage, the program uses a .env file to give the base (root) directory where the files to respond are located. I am trying to write a test case to ensure that the NestJS ConfigService is working properly, but also to use as a base for all my other tests.

With different routes, different data files need to be returned. My code will need to mock all these files. As this data relies on the base ConfigService having read the .env to find the base paths, my routes are based on this starting point.

During development, I have a local .env file with these values set. However, I want to test without this .env file being used, so my tests do not rely on the presence of the .env file, since the CI/CD server, build server, etc., will not have a .env file.

I am simply trying a test file then to work with the configuration, to get set data from my mock.

nestjs-config.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule, ConfigService } from '@nestjs/config';

describe('NestJS Configuration .env', () => {
    let service: ConfigService;

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            imports: [
                ConfigModule.forRoot({
                    expandVariables: true,
                }),
            ],
            providers: [
                {
                    provide: ConfigService,
                    useValue: {
                        get: jest.fn((key: string) => {
                            if (key === 'FILES') {
                                return './fakedata/';
                            } else if (key === 'PORT') {
                                return '9999';
                            }
                            return null;
                        }),
                    },
                },
            ],
        }).compile();

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

    it('should be defined', () => {
        expect(service).toBeDefined();
    });

    it.each([
        ['FILES=', 'FILES', './', './fakedata/'],
        ['PORT=', 'PORT', '2000', '9999'],
        ['default value when key is not found', 'NOTFOUND', './', './'],
    ])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
        const Result: string = service.get<string>(Key, Default);
        expect(Key).toBeDefined();
        expect(Result).toBe(Expected);
    });
});

The problem in this test is the default values are always returned, meaning the .env file was not read, but the provider had the code to handle this.

Ideally, I would like to create a fake class for testing so I could use it in all my test files. However, when trying to create the fake class, I get an error about other methods missing, that are unrelated to this class.

export class ConfigServiceFake {
    get(key: string) {
        switch (key) {
            case 'FILES':
                return './fakedata/';
            case 'PORT':
                return '9999';
        }
    }
}

This does not seem to execute, and it appears to still go through the original service.

Upvotes: 1

Views: 5357

Answers (1)

Steven Scott
Steven Scott

Reputation: 11260

I was able to adjust this and not need external references, including importing the configuration module, making the mock simpler, and not needing the full definitions.

import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';

describe('NestJS Configuration .env', () => {
    let service: ConfigService;

    afterEach(() => {
        jest.clearAllMocks();
    });

    beforeEach(async () => {
        const FakeConfigService = {
            provide: ConfigService,
            useValue: {
                get: jest.fn((Key: string, DefaultValue: string) => {
                    switch (Key) {
                        case 'FILES':
                            return './fakedata/';
                            break;
                        case 'PORT':
                            return '9999';
                            break;
                        default:
                            return DefaultValue;
                    }
                }),
            },
        };

        const module: TestingModule = await Test.createTestingModule({
            providers: [FakeConfigService],
        }).compile();

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

    it('should be defined', () => {
        expect(service).toBeDefined();
    });

    it.each([
        ['FILES=', 'FILES', './', './fakedata/'],
        ['PORT=', 'PORT', '2000', '9999'],
        ['default value when key is not found', 'NOTFOUND', './', './'],
    ])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
        const Result: string = service.get<string>(Key, Default);
        expect(Key).toBeDefined();
        expect(Result).toBe(Expected);
    });
});

Upvotes: 4

Related Questions