最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular Unit Test including FileReader.onload doesn't work with asyncwhenStable nor fakeAsynctick - Stack O

programmeradmin2浏览0评论

I'm adding some unit tests to my angular 7 app but I'm not sure how to handle the interaction with a FileReader since using async/whenStable, fakeAsync and promises don't work as expected.

  • The behavior I'm testing: I need to verify the service documentService.sendDocument is called once a file is uploaded.

1st Approach:

Before I found issues testing FileReader.onload I just tried to test it without async.

Controller Code

onFileGovernmentIdChange(event) {
    console.log('::: Init onFileGovernmentIdChange');
    const updateFunction = () => {
        console.log('::: BEFORE update fileGovernmentIdUploaded - ', this.fileGovernmentIdUploaded);
        this.fileGovernmentIdUploaded = true;
        console.log('::: AFTER update fileGovernmentIdUploaded - ', this.fileGovernmentIdUploaded);
    }
    this.saveFileFromInputSimple(event, DOCUMENT_TYPE.STORE_GOVERNMENT_ID, updateFunction);
    console.log('::: End onFileGovernmentIdChange');
}

private saveFileFromInputSimple(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputSimple');
    const reader = new FileReader();

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];
        reader.onload = () => {
            this.documentService
                .sendDocument(file.name, file.type, reader.result.toString(), documentType)
                .subscribe(
                    response => {
                        console.log('::: sendDocument - Subscribe OK');
                        updateState()
                    },
                    error => {
                        console.log('::: sendDocument - Subscribe ERROR');
                        this.showDefaultErrorDialog()
                    }
                );
        };
        console.log('::: Onload callback assigned...');
        reader.readAsDataURL(file);
    }
    console.log('::: End saveFileFromInputSimple');
}


UnitTest spec code

fit('simple testing', () => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponsemissionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documentsponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:387:1)

UnitTest analysis

The code inside the onload method is never executed

2nd Approach

Testing FileReader.onload without Observers: Just to follow the workflow I removed the Observer inside the onload to verify if that could be the problem.

Controller Code

private saveFileFromInputSimple(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputSimple');
    const reader = new FileReader();

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];
        reader.onload = () => {
            console.log('::: ONLOAD executed');
        };
        console.log('::: Onload callback assigned...');
        reader.readAsDataURL(file);
    }
    console.log('::: End saveFileFromInputSimple');
}

UnitTest spec code

Same as 1st approach

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documentsponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:387:1)
Chrome 71.0.3578 (Mac OS X 10.13.6): Executed 1 of 46 (1 FAILED) (0 secs / 0.314 secs)
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documentsponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
LOG: '::: ONLOAD executed'

UnitTest analysis

Now the code in the onload method is executed but looks like an async operation because it was executed at the end.

3rd Approach

Testing FileReader.onload with async unit test: As I found some kind of async operation I included the async/fixture.whenStable angular bination to wait for async code pletion.

Controller Code

Same as 2nd approach

UnitTest spec code

fit('Testing with async/fixture.whenStable', async(() => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponsemissionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');

    fixture.whenStable().then(() => {
        fixture.detectChanges();
        console.log('::: whenStable Init');
        expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
        // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
        console.log('::: whenStable End');
    });

    console.log('::: After expects... ');
}));

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
LOG: '::: whenStable Init'
LOG: '::: whenStable End'
**LOG: '::: ONLOAD executed'**

UnitTest analysis

As expected, the whenStable code is executed when the method has finished, however, the onload method continues executing at the end. Googling I found that could be better to wrap the onload part in a Promise to be sure it's tracked by Angular async.

4th Approach:

Wrap the onload in a promise and verify it works with the async/whenStable structure.

Controller Code

private readFileAsync(file): Promise<string> {
    console.log('::: readFileAsync Init');
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
            console.log(':::: Promise Resolved in ONLOAD')
            resolve(reader.result.toString());
        }
        reader.onerror = reject;
        reader.readAsDataURL(file);
    });
}

private saveFileFromInputWithPromise(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputWithPromise');

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];

        this.readFileAsync(file)
            .then(fileContent => {
                console.log('::: File with content', fileContent);
                updateState();
            }).catch(error => console.log('::: File load error', error));
    }
    console.log('::: End saveFileFromInputWithPromise');
}

UnitTest spec code

Same as 3rd approach

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputWithPromise'
LOG: '::: Event with files...'
LOG: '::: readFileAsync Init'
LOG: '::: End saveFileFromInputWithPromise'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
LOG: '::: whenStable Init'
LOG: '::: whenStable End'
LOG: ':::: Promise Resolved in ONLOAD'
LOG: '::: File with content', 'data:pdf;base64,c3NkZnNkZ2RqZ2hkc2xramdoZGpn'
LOG: '::: BEFORE update fileGovernmentIdUploaded - ', undefined
LOG: '::: AFTER update fileGovernmentIdUploaded - ', true
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent Testing with async/fixture.whenStable FAILED
        Expected undefined to be truthy.
            at http://localhost:9877/_karma_webpack_/webpack:/src/app/documents/documentsponent.spec.ts:176:56
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at AsyncTestZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.AsyncTestZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:713:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:285:1)

UnitTest analysis

Again the Promise is resolved after the whenStable function.

5th Approach:

As the async/whenStable didn't work as expected I tried changing to fakeAsync/tick/flush structure.

Controller Code

Same as 4th Approach (Including onload in a promise)

UnitTest spec code

fit('Testing with fakeAsync/tick/flush', fakeAsync(() => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponsemissionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputWithPromise'
LOG: '::: Event with files...'
LOG: '::: readFileAsync Init'
LOG: '::: End saveFileFromInputWithPromise'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After Flush Init'
LOG: '::: After Flush End'
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent Testing with async/fixture.whenStable FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documentsponent.spec.ts:211:52)
            at UserContext.<anonymous> node_modules/zone.js/dist/zone-testing.js:1424:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
LOG: ':::: Promise Resolved in ONLOAD'
LOG: '::: File with content', 'data:pdf;base64,c3NkZnNkZ2RqZ2hkc2xramdoZGpn'
LOG: '::: BEFORE update fileGovernmentIdUploaded - ', undefined
LOG: '::: AFTER update fileGovernmentIdUploaded - ', true

UnitTest analysis

Again, the Promise is resolved after the fakeAsync/tick/flushMicrotasks/flush structure

What is wrong?

I followed every tutorial I found and every different approach trying to include the FileReader.onload in my test (as that method call the service I want to spy and verify) but always the method resolve after the async blocks provided by Angular. I saw other approaches where the window.fileReader is mocked but that's not the purpose of my test.

So could anybody tell me what is wrong in my code or in the way I'm testing?

I'm adding some unit tests to my angular 7 app but I'm not sure how to handle the interaction with a FileReader since using async/whenStable, fakeAsync and promises don't work as expected.

  • The behavior I'm testing: I need to verify the service documentService.sendDocument is called once a file is uploaded.

1st Approach:

Before I found issues testing FileReader.onload I just tried to test it without async.

Controller Code

onFileGovernmentIdChange(event) {
    console.log('::: Init onFileGovernmentIdChange');
    const updateFunction = () => {
        console.log('::: BEFORE update fileGovernmentIdUploaded - ', this.fileGovernmentIdUploaded);
        this.fileGovernmentIdUploaded = true;
        console.log('::: AFTER update fileGovernmentIdUploaded - ', this.fileGovernmentIdUploaded);
    }
    this.saveFileFromInputSimple(event, DOCUMENT_TYPE.STORE_GOVERNMENT_ID, updateFunction);
    console.log('::: End onFileGovernmentIdChange');
}

private saveFileFromInputSimple(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputSimple');
    const reader = new FileReader();

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];
        reader.onload = () => {
            this.documentService
                .sendDocument(file.name, file.type, reader.result.toString(), documentType)
                .subscribe(
                    response => {
                        console.log('::: sendDocument - Subscribe OK');
                        updateState()
                    },
                    error => {
                        console.log('::: sendDocument - Subscribe ERROR');
                        this.showDefaultErrorDialog()
                    }
                );
        };
        console.log('::: Onload callback assigned...');
        reader.readAsDataURL(file);
    }
    console.log('::: End saveFileFromInputSimple');
}


UnitTest spec code

fit('simple testing', () => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponse.missionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documents.ponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:387:1)

UnitTest analysis

The code inside the onload method is never executed

2nd Approach

Testing FileReader.onload without Observers: Just to follow the workflow I removed the Observer inside the onload to verify if that could be the problem.

Controller Code

private saveFileFromInputSimple(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputSimple');
    const reader = new FileReader();

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];
        reader.onload = () => {
            console.log('::: ONLOAD executed');
        };
        console.log('::: Onload callback assigned...');
        reader.readAsDataURL(file);
    }
    console.log('::: End saveFileFromInputSimple');
}

UnitTest spec code

Same as 1st approach

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documents.ponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:387:1)
Chrome 71.0.3578 (Mac OS X 10.13.6): Executed 1 of 46 (1 FAILED) (0 secs / 0.314 secs)
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent simple testing FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documents.ponent.spec.ts:146:52)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
LOG: '::: ONLOAD executed'

UnitTest analysis

Now the code in the onload method is executed but looks like an async operation because it was executed at the end.

3rd Approach

Testing FileReader.onload with async unit test: As I found some kind of async operation I included the async/fixture.whenStable angular bination to wait for async code pletion.

Controller Code

Same as 2nd approach

UnitTest spec code

fit('Testing with async/fixture.whenStable', async(() => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponse.missionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');

    fixture.whenStable().then(() => {
        fixture.detectChanges();
        console.log('::: whenStable Init');
        expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
        // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
        console.log('::: whenStable End');
    });

    console.log('::: After expects... ');
}));

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputSimple'
LOG: '::: Event with files...'
LOG: '::: Onload callback assigned...'
LOG: '::: End saveFileFromInputSimple'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
LOG: '::: whenStable Init'
LOG: '::: whenStable End'
**LOG: '::: ONLOAD executed'**

UnitTest analysis

As expected, the whenStable code is executed when the method has finished, however, the onload method continues executing at the end. Googling I found that could be better to wrap the onload part in a Promise to be sure it's tracked by Angular async.

4th Approach:

Wrap the onload in a promise and verify it works with the async/whenStable structure.

Controller Code

private readFileAsync(file): Promise<string> {
    console.log('::: readFileAsync Init');
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
            console.log(':::: Promise Resolved in ONLOAD')
            resolve(reader.result.toString());
        }
        reader.onerror = reject;
        reader.readAsDataURL(file);
    });
}

private saveFileFromInputWithPromise(event, documentType: DOCUMENT_TYPE, updateState: () => any) {
    console.log('::: Init saveFileFromInputWithPromise');

    if (event.target.files && event.target.files.length > 0) {
        console.log('::: Event with files...');
        const file = event.target.files[0];

        this.readFileAsync(file)
            .then(fileContent => {
                console.log('::: File with content', fileContent);
                updateState();
            }).catch(error => console.log('::: File load error', error));
    }
    console.log('::: End saveFileFromInputWithPromise');
}

UnitTest spec code

Same as 3rd approach

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()'
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputWithPromise'
LOG: '::: Event with files...'
LOG: '::: readFileAsync Init'
LOG: '::: End saveFileFromInputWithPromise'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After expects... '
LOG: '::: whenStable Init'
LOG: '::: whenStable End'
LOG: ':::: Promise Resolved in ONLOAD'
LOG: '::: File with content', 'data:pdf;base64,c3NkZnNkZ2RqZ2hkc2xramdoZGpn'
LOG: '::: BEFORE update fileGovernmentIdUploaded - ', undefined
LOG: '::: AFTER update fileGovernmentIdUploaded - ', true
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent Testing with async/fixture.whenStable FAILED
        Expected undefined to be truthy.
            at http://localhost:9877/_karma_webpack_/webpack:/src/app/documents/documents.ponent.spec.ts:176:56
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at AsyncTestZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.AsyncTestZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:713:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:285:1)

UnitTest analysis

Again the Promise is resolved after the whenStable function.

5th Approach:

As the async/whenStable didn't work as expected I tried changing to fakeAsync/tick/flush structure.

Controller Code

Same as 4th Approach (Including onload in a promise)

UnitTest spec code

fit('Testing with fakeAsync/tick/flush', fakeAsync(() => {
    const documentService = TestBed.get(DocumentsService);
    const catalogService: CatalogService = TestBed.get(CatalogService);
    const customEvent = {
        target: {
            files: [new Blob(['ssdfsdgdjghdslkjghdjg'], { type: 'pdf' })]
        }
    };

    const merceResponse = new CommerceResponse();
    merceResponse.missionPercentage = '11';

    spyOn(catalogService, 'getCatalog').and.returnValue(of({ catalogs: [] }));
    spyOn(documentService, 'getCommerceInfo').and.returnValue(of(merceResponse));
    spyOn(documentService, 'sendDocument').and.returnValue(of({ response: 'ok' }));

    fixture.detectChanges();
    console.log('::: Before calling onFileGovernmentIdChange()');
    ponent.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(ponent.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));

UnitTest result

LOG: '::: Before calling onFileGovernmentIdChange()
LOG: '::: Init onFileGovernmentIdChange'
LOG: '::: Init saveFileFromInputWithPromise'
LOG: '::: Event with files...'
LOG: '::: readFileAsync Init'
LOG: '::: End saveFileFromInputWithPromise'
LOG: '::: End onFileGovernmentIdChange'
LOG: '::: After calling onFileGovernmentIdChange()'
LOG: '::: Before expects... '
LOG: '::: After Flush Init'
LOG: '::: After Flush End'
LOG: '::: After expects... '
Chrome 71.0.3578 (Mac OS X 10.13.6) DocumentsComponent Testing with async/fixture.whenStable FAILED
        Expected undefined to be truthy.
            at UserContext.<anonymous> src/app/documents/documents.ponent.spec.ts:211:52)
            at UserContext.<anonymous> node_modules/zone.js/dist/zone-testing.js:1424:1)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone-testing.js:288:1)
LOG: ':::: Promise Resolved in ONLOAD'
LOG: '::: File with content', 'data:pdf;base64,c3NkZnNkZ2RqZ2hkc2xramdoZGpn'
LOG: '::: BEFORE update fileGovernmentIdUploaded - ', undefined
LOG: '::: AFTER update fileGovernmentIdUploaded - ', true

UnitTest analysis

Again, the Promise is resolved after the fakeAsync/tick/flushMicrotasks/flush structure

What is wrong?

I followed every tutorial I found and every different approach trying to include the FileReader.onload in my test (as that method call the service I want to spy and verify) but always the method resolve after the async blocks provided by Angular. I saw other approaches where the window.fileReader is mocked but that's not the purpose of my test.

So could anybody tell me what is wrong in my code or in the way I'm testing?

Share Improve this question edited Feb 11, 2019 at 16:53 Hernán Tenjo asked Feb 10, 2019 at 0:43 Hernán TenjoHernán Tenjo 1982 silver badges13 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 6

Great post, perfectly describing my evening.

It seems it is a known issue that fakeAsync does not support FileReader, as "it is not fundamentaly a timer related async operation".

See: https://github./angular/zone.js/issues/1120

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论