Search code examples
angulartypescriptunit-testingkarma-jasminespyon

How to SpyOn function inside Function and return Fake value


This Question seems to be same but definitely not duplicate.

In one of my function i am calling this.cordovaFile.readAsArrayBuffer(this.cordovaFile.dataDirectory, key)

this cordovafile.readAsArrayBuffer() reads data from ipad File storage, but in Unit test i cant do that since it runs in Browsers, so i am using SpyOn to get fake value, i am getting difficulties to do that.

can some one help me below is the Function

    private getTimesheetByKey(key: TIMESHEET_KEYS): Observable<TimesheetModel> {
        let timesheet: TimesheetModel;

        return from(
            this.cordovaFile
                .readAsArrayBuffer(this.cordovaFile.dataDirectory, key)
                .then(compressedConfirmation => {
                    const start = moment();
                    const uint8Array = new Uint8Array(compressedConfirmation);
                    const jsonTimeSheet = this.LZString.decompressFromUint8Array(uint8Array);

                    timesheet = new TimesheetModelFromJson(<JsonTimesheetModel>(
                        JSON.parse(jsonTimeSheet)
                    ));
                    this.saveTimesheetByKey(key, timesheet);
                    return timesheet;
                })
                .catch((error: Error) => {})
        );
    }

and This is how the Unit Test what i am trying to write, I dont know exactly how to SpyOn this.cordovaFile.readAsArrayBuffer(this.cordovaFile.dataDirectory, key) and returns value

it('should save the Timesheet to file storage', () => {
        spyOn(LocalStorageTimesheetService, 'getTimesheetByKey')
            .and.callThrough(cordovaFile.readAsArrayBuffer())
            .and.returnValue(timesheet);

        expect(timesheet).toEqual();
    });

this.cordovaFile.readAsArrayBuffer() should return fake value something like below

timesheet = {
            startOfWork: '2019-07-02T02:00:00.000Z',
            notifications: [],
            additionalExpenses: [],
            manualTimesheetEntries: [],
            endOfWork: undefined,
            isSubmitted: false,
            attendanceType: 'FREE',
        };

Solution

  • I dont think spyOn(LocalStorageTimesheetService, 'getTimesheetByKey') will work because getTimesheetByKey is a private function. Make it public first.

    Also, by spying the readAsArrayBuffer, you will be able to control the outcome which is compressedConfirmation and not timesheet. timesheet is calculated after performing new Uint8Array() etc etc operations over compressedConfirmation

    If you are worried about just checking if the value is saved when we call getTimesheetByKey then you can write it like:

    it('should call "saveTimesheetByKey()" when getTimesheetByKey() is called', () => {
        const compressedConfirmationMock = 'some_val'; // expected Mock value of compressedConfirmation
        spyOn(LocalStorageTimesheetService.cordovaFile, 'readAsArrayBuffer').and.returnValue(Promise.resolve(compressedConfirmationMock));
        spyOn( LocalStorageTimesheetService, 'saveTimesheetByKey').and.callThrough();
        LocalStorageTimesheetService.getTimesheetByKey(SOME_VAL_OF_TYPE_TIMESHEET_KEYS);
        expect(LocalStorageTimesheetService.saveTimesheetByKey).toHaveBeenCalled();
    });
    
    
    it('should save the Timesheet to file storage using saveTimesheetByKey()', () => {
        // here unit test the logic of saveTimesheetByKey() function 
        // which is actually saving the data. 
    })
    
    
    it('should perform some logic where getTimesheetByKey() is called', () => {
       // improve the "it" statement. 
       spyOn(LocalStorageTimesheetService, 'getTimesheetByKey')
            .and.returnValue(of({
                startOfWork: '2019-07-02T02:00:00.000Z',
                notifications: [],
                additionalExpenses: [],
                manualTimesheetEntries: [],
                endOfWork: undefined,
                isSubmitted: false,
                attendanceType: 'FREE',
            }));
       // and here write unit test that function which is using value of `timesheet` returned by calling "getTimesheetByKey()"
       //  expect block here
    })
    

    Please note the way I have broken your unit test logic to 3 different blocks. Unit test should be more isolated tests and should be written on granular level of function logic.