Home > Software design >  Typescript/Jest mocking a shared function from a service
Typescript/Jest mocking a shared function from a service

Time:09-30

I have a service for NestJS, but this is not really a NestJS issue. However, NestJS testing is in play, and I use it to create the service for testing, etc. My service is used to make a number of calls, and return all the data.

In my scenario, there is a service (myservice) which will get some results (an object of data) that combines results from various sources. The external sources are all Promise (async/await) based, and are used to get all the different data points, combine this into the myservice (a base service that is extended by others) to combine the error handling, promise result processing, etc.

I am having an issue trying to mock the external calls for testing. My issue is for the function that reads data from NodeJS. I can test the function on its own without issue, but now I am trying to mock out this function call in my service.

A short sample of the functions in questions, as part of: myservice.ts

public async GetData(DataType: number = DATA_TYPES.TEXT): Promise<ResultFile> {
    const ResultData: ResultFile = new ResultFile(DataType);

    return new Promise<ResultFile>((resolve, reject) => {
        const Data$: Promise<string> = this.GetResultFileContents();
        const StatusCode$: Promise<number> = this.GetResultFileStatusCode();

        Promise.all([Data$, StatusCode$])
            .then((Results) => {
                ResultData.Contents = Results[0]; // Promise returns a string
                ResultData.StatusCode = Results[1]; // Promise returns a number

                resolve(ResultData);
            })
            .catch((Exception: Error) => {
                ResultData.StatusCode = HttpStatus.NOT_FOUND;
                ResultData.Contents = Exception.message;
                reject(ResultData);
            });
    });
}

The above is the main method trying to retrieve different data. This calls more promises than the 2 that are there, but two will show my issue.

public async GetResultFileContents(): Promise<string> {
    try {
        const Results$: string = await ReadFileContents(this.Directory_, this.BaseName_);
        return Results$;
    } catch (Exception) {
        throw new HttpException(`File not found: ${this.BaseName_} in ${this.Directory_}`, HttpStatus.NOT_FOUND);
    }
}

public async GetResultFileStatusCode(): Promise<number> {
    let StatusCode: number = 0;
    try {
        const StatusCodeFile: string = `${this.BaseName_}.err`;
        const StatusCodeData$ = await ReadFileContents(this.Directory_, StatusCodeFile);
        StatusCode = GetIntegerFromText(StatusCodeData$);
    } catch (Exception) {
        StatusCode = HttpStatus.OK; // Return OK since a specific status was not set to be returned.
    }
    return new Promise<number>((resolve) => {
        resolve(StatusCode);
    });
}

The two methods that are called, to return promises, both use an external function, ReadFileContents(). This is the function I want to mock, as it can return the data as a string, or throw an exception, wrapping the OS checks (among other things) for the file(s) containing the data.

This function is common, and shared by a number of the methods that read data from the file system. There is also something similar to REST calls, which have the same issue, but this is a simple example.

My issue now comes in the test file. While I can test the service, and the methods within the myservice.ts, but I do not know how to mock the external call to ReadFileContents() to ensure my methods in the myservice.ts are executing properly.

I want to test different return strings, as well as the catch of exceptions when files do not exist, etc.

My test:

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

import { MyService } from './my.service';

// TODO: Need to mock the internal calls to ReadFileContents() to throw exceptions
describe('MyService (mock)', () => {
    let service: MyService;

    afterEach(() => {});

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

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

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

    it('should handle exceptions when reading missing .hdr file', async () => {
        // const mockReadFileContents = jest.spyOn(service.???);
    });
});

In the last function, I do not know how to mock the ReadFileContents, as it is just a function within the service, which is in another source file.

I really do not want make a public method in the service, to simply call this function, so I can mock it, if I can help that.

Please ignore missing functions, or other typos as this was a quick edit to provide the sample of what I am trying to accomplish.

CodePudding user response:

I have continued to work on this, and could not get the mock to work. I did then change the functions I was trying to mock, to be in another service (fsService) and used this service in the myService, via the constructor with Dependency Injection.

constructor(
    private FileSystem_: fsService,
) {}

This can then be easily mocked in a test by either mocking the service, providing a fake definition ( {provide: fsService, useClass: fsServiceMock }) etc.

import { Test, TestingModule } from '@nestjs/testing';
import { MyService } from './my.service';
import { FsService } from './fs.service';

describe('MyService (mock)', () => {
    let service: MyService;
    let FSService_: FsService;

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

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [
                FSService,
                MyService,
            ],
        }).compile();
        FsService_ = module.get<FSService>(FSService);
        service = module.get<MyService>(MyService);
    });

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

    it('should handle exceptions when reading missing .hdr file', async () => {
        FSService_.ReadFileContents = jest.fn().mockRejectedValue(new Error('ENOENT: File not Found'));
        ...
    });
});
  • Related