Testing asynchronous functions within asynchronous functions is achievable through NestJS with the help of Jest

I've been working on writing a test for my service, and while everything seems to be functioning correctly (both the service and the tests thus far), I'm encountering an issue with verifying that a specific function is being called. Despite this failure, the code coverage output from the test indicates that the line is indeed being executed, and running the code itself confirms that it does delete the data.

To better illustrate my problem, I've attempted to extract the relevant portion of the service processing. The core functionality of my service involves managing holiday dates retrieved from an external source. This process involves eliminating invalid entries and adding new holiday date records for each upcoming year. Below are the simplified versions of the functions causing me trouble:

export class UpdateService {
    constructor(
        private readonly HolidayDateDataService_: HolidayDatesService,
    ) {
        this.RemoveInvalidHolidayDateEntriesForAYear(Year);
    }

    async RemoveInvalidHolidayDateEntriesForAYear(Year: number): Promise<void> {
        let ExistingHolidayDates: Array<HolidayDatesResponseDTO>;

        try {
            ExistingHolidayDates = await this.HolidayDateDataService_.GetAllRecordsByYear(Year); // From external API
        } catch (Exception) {
            return;
        }

        ExistingHolidayDates.forEach( async (OneDateEntry: HolidayDatesResponseDTO) => {
            const HolidayIdIsActive: boolean = await this.ValidateHolidayIdActive_(OneDateEntry.HolidayId); 
            if (! HolidayIdIsActive) {
                await this.DeleteOneHolidayDateRecord_(OneDateEntry);
            }
        });
        return;
    }

    async DeleteOneHolidayDateRecord_(HolidayDateData: HolidayDatesResponseDTO): Promise<void> {
        console.log('Start DeleteOneHolidayDateRecord_');
        try {
            console.log('Start HolidayDateDataService');
            await this.HolidayDateDataService_.Delete(
                new HolidayDatesEntity(HolidayDateData.HolidayId, HolidayDateData.Name, HolidayDateData.Year),
                HolidayDateData.Key_
            );
            console.log('End HolidayDateDataService');
        } catch (Exception) {
            ;
        }
        console.log('End DeleteOneHolidayDateRecord_');
        return;
    }

    async ValidateHolidayIdActive_(HolidayId: string): Promise<boolean> {
        console.log('Start ValidateHolidayIdActive_');
        try {
            console.log('Start HolidayIdDateService');

            const HolidayIdData: HolidayIdsResponseDTO = await this.HolidayIdDataService_.GetOne(HolidayId);

            console.log('End HolidayIdDateService');
            if (HolidayIdData && HolidayIdData.Active) {
                return true;
            }
        } catch (Exception) {
            ;
        }
        console.log('End ValidateHolidayIdActive_');
        return false;
    }
}

Although the above code works fine, I am struggling to test and confirm that the delete operation is actually being triggered.

describe('UpdateService', () => {
    let service: UpdateService;
    let HolidayIdsTestService: HolidayIdsService;
    let HolidayDatesTestService: HolidayDatesService;

    const HolidayRecords: Array<HolidaysApi> = new Array<HolidaysApi>(
        { id: 31, name: 'Christmas Day', observedDate: '2022-12-27' } as HolidaysApi,
        { id: 32, name: 'Boxing Day', observedDate: '2022-12-28' } as HolidaysApi,
    );


    beforeEach(async () => {
        const mockHolidayIdsService = {
            provide: HolidayIdsService,
            useValue: {
                GetOne: jest.fn(),
            },
        };

        const mockHolidayDatesService = {
            provide: HolidayDatesService,
            useFactory: () => ({
                Delete: jest.fn(),
                GetAllRecordsByYear: jest.fn(),
            }),
        };

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

        service = module.get<UpdateService>(UpdateService);
        HolidayIdsTestService = module.get<HolidayIdsService>(HolidayIdsService);
        HolidayDatesTestService = module.get<HolidayDatesService>(HolidayDatesService);
    });

    it('should delete HolidayDate records if ValidateHolidayIdActive_ returns false', async () => {
        jest.spyOn(HolidayDatesTestService, GetAllRecordsByYear').mockResolvedValue(HolidayDateRecords);
        jest.spyOn(HolidayIdsTestService, 'GetOne').mockResolvedValue(new HolidayIdsResponseDTO('Fake_Holiday_Id', false, 31, 'Christmas Day'));
        jest.spyOn(HolidayDatesTestService, 'Delete').mockResolvedValue();

        await service.RemoveInvalidHolidayDateEntriesForAYear(2022);
        expect(HolidayIdsTestService.GetOne).toHaveBeenCalledTimes(2);
        expect(HolidayDatesTestService.Delete).toHaveBeenCalledTimes(2);
    });
});

The issue in my testing lies in the fact that the last expectation fails:

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

  532 |             await service.RemoveInvalidHolidayDateEntriesForAYear(2022);
  533 |             expect(HolidayIdsTestService.GetOne).toHaveBeenCalledTimes(2);
> 534 |             expect(HolidayDatesTestService.Delete).toHaveBeenCalledTimes(2);
      |                                                    ^                                                                      

The function ValidateHolidayIdActive_ is being called twice (internally using HoldiayIdsTestService which is mocked), as expected. It then should lead to DeleteOneHolidayDateRecord_ being called twice. However, both .toHaveBeenCalled() and .toHaveBeenCalledTimes(2) fail when trying to verify whether the internal service is being invoked, although it has been mocked.

Upon inspecting the series of console.log() statements within the code, it appears that the modules are being appropriately activated and completed in the correct sequence.

Log for Record #1

console.log Start ValidateHolidayIdActive_
console.log Start HolidayIdDateService
console.log Start ValidateHolidayIdActive_
console.log Start HolidayIdDateService

console.log End HolidayIdDateService
console.log End ValidateHolidayIdActive_
console.log End HolidayIdDateService
console.log End ValidateHolidayIdActive_

Log for Record #2

console.log   Start DeleteOneHolidayDateRecord_
console.log   Start HolidayDateDataService
console.log   Start DeleteOneHolidayDateRecord_
console.log   Start HolidayDateDataService

console.log   End HolidayDateDataService
console.log   End DeleteOneHolidayDateRecord_
console.log   End HolidayDateDataService
console.log   End DeleteOneHolidayDateRecord_

Despite apparently functioning correctly, one function shows as operational under spying, while the other does not. I can't seem to pinpoint where my mistake lies.

Any assistance would be greatly appreciated as I believe it could be a simple oversight, yet it's proving to be quite perplexing and elusive. Originally, all functionalities were encapsulated within a single function, but I decided to break them down to test each individual function separately (with similar mock setups).

Answer №1

My current problem lies in the processing flow I am implementing. Using await/async with loops that involve callbacks, like forEach(), is causing issues for me. Switching to the for..of loop structure will help resolve the errors I'm facing. While it does require some code restructuring, it effectively addresses the problem at hand.

...
for (OneDateEntry of ExistingHolidayDates) => {
    const HolidayIdIsActive: boolean = await 
    this.ValidateHolidayIdActive_(OneDateEntry.HolidayId); // Validates Entry should be maintained.  
    if (! HolidayIdIsActive) {
        await this.DeleteOneHolidayDateRecord_(OneDateEntry);
        }
    });
});

By making this change from using a callback in forEach to utilizing for..of loop, the processing will proceed correctly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring the potential of lazy loading with AngularJS 2 RC5 ngModule

I am currently utilizing the RC5 ngModule in my Angular project. In my app.module.ts file, the import statements and module setup are as follows: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/plat ...

What is the process of creating the /dist folder in an unreleased version of an npm package?

Currently working on implementing a pull request for this module: https://github.com/echoulen/react-pull-to-refresh ... It seems that the published module generates the /dist folder in the package.json prepublish npm script. I have my local version of the ...

Getting the value from an array of objects in React Redux Toolkit

Task: const postsData = useSelector((state: RootState) => state.postsSlice.posts); { postsData?.map((post) => { return ( <PostCard key={post.id} title={post.title} description={post.body} user={post.use ...

What is the reason behind Typescript executing the abstract class before anything else?

I'm currently facing a challenge solving an abstract class problem with Typescript. Let me explain what I am trying to accomplish. There is a class named Sword that extends Weapon. Each Weapon must have certain properties like the damage, but since e ...

Concealing the Submit Button During Server Processing (Issues with Binding?)

My Angular 2 form is set up to send data to the server asynchronously. I want to provide users with visual feedback during the waiting period by changing the blue 'submit' button to a greyed-out 'Please wait...' button. To achieve this, ...

GPT Open-Source Initiative - Data Ingestion Challenge

Seeking assistance with setting up the following Open Source project: https://github.com/mayooear/gpt4-pdf-chatbot-langchain Encountering an error when running the npm run ingest command: Error [Error: PineconeClient: Error calling upsert: Error: Pinecon ...

Verify registration by sending an email to an alternate email address through Angular Firebase

I have implemented email verification for users before registration. However, I would like to receive a verification email to my own email address in order to finalize the registration process. I want to be notified via email and only after my approval sho ...

What is the best way to distinguish TypeScript types?

How should I handle comparing typescript types in this particular case? interface TableParams extends TableProps { data: Array<object> | JSX.Element } export const BasicTable = ({ data}: TableParams) => { if(typeof data == Array<object ...

Error in TypeScript: The type 'event' is lacking the following properties compared to type 'KeyboardEvent'

I've created a custom event listener hook with the following structure: const useEventListener = ( eventName: string, handler: ({key} : KeyboardEvent) => void, ) => { const savedHandler = useRef<({key} : KeyboardEvent) => void>(ha ...

The canActivate function must be responsive to the true or false value of this.authService.isLoggedIn before proceeding

I am facing a problem with my app's routing functionality. When the user logs in with their Google email, I want the app to route to the home page: "". Everything seems to work fine except for the canActivate routeGuard. During the AuthLogin ...

Issue arose while attempting to use Jest on a React Native application integrated with TypeScript (Jest has come across an unforeseen token)

Seems like everyone and their grandmother is facing a similar issue. I've tried everything suggested on Stack Overflow and GitHub, but nothing seems to work. It should be a simple fix considering my project is basic and new. Yet, I can't seem to ...

The promise ended up on a different path

I have a function implemented to update my user on firebase, but for some reason the "then" function is never called. Any ideas on how to resolve this issue? The user's data was successfully updated in the database, and when I try to directly impleme ...

Understanding a compound data type in TypeScript

Hey there, I'm new to TypeScript and I'm facing a challenge in defining the type for an object that might have the following structure at runtime: { "animals" : [ { name: "kittie", color: "blue" }, { name: ...

The attribute 'initGradient' is not defined within this context

Struggling to incorporate a gradient background into my React and Typescript website following a guide on "https://kevinhufnagl.com/how-to-stripe-website-gradient-effect/". The error message "Property 'initGradient' does not exist on type 'G ...

Combining results from multiple subscriptions in RxJS leads to a TypeScript compiler error

I am utilizing an Angular service that provides a filterObservable. To combine multiple calls, I am using Rx.Observable.zip(). Although it functions as expected, my TypeScript compiler is throwing an error for the method: error TS2346: Supplied paramete ...

Typescript's conditional types define unique object fields based on specified conditions

I am looking to create a function (which will eventually be used as a React function component) in Typescript that receives a props object containing a list of lists of objects of any type. The goal is for the function to output a "key" for each object - i ...

What is the best way to distribute multiple Typescript interfaces among different projects?

I've created a project with TypeScript interfaces exclusively. For example, I have separate files like account.ts and item.ts that solely consist of interfaces. export interface Account { name: string; } Now, I am wondering how I can package all ...

Issues with verifying input of properties in TypeScript React components are not being

I am grappling with a typescript issue as I am fairly new to it. My problem lies in the fact that I have defined an interface and I am validating the props. It works fine when the props are empty, but I am supposed to receive a string and if a number is pa ...

How should I handle a situation where the placeholder type is set as a string, but the actual value is a number?

Within my TSX file, I have a component that serves as an input. import { InputHTMLAttributes } from "react"; interface InputProps extends InputHTMLAttributes<HTMLInputElement> { placeholder: string, label?: string, } export const ...

Create a TypeScript module that exports classes from multiple files

I've been facing a challenge with splitting TypeScript modules containing classes into separate files. Despite searching for solutions, none have resolved my specific issue. Currently, I have a module called store that contains two classes: Person an ...