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).