Javascript 角度单元测试,包括FileReader.onload不';不能使用async/whenStable或fakeAsync/tick

Javascript 角度单元测试,包括FileReader.onload不';不能使用async/whenStable或fakeAsync/tick,javascript,angular,jasmine,karma-jasmine,filereader,Javascript,Angular,Jasmine,Karma Jasmine,Filereader,我正在为angular 7应用程序添加一些单元测试,但我不确定如何处理与文件读取器的交互,因为使用async/whenStable、fakeAsync和Promissions无法按预期工作 我正在测试的行为:我需要验证服务documentService。上传文件后会调用sendDocument 第一次进近: 在我发现测试FileReader.onload的问题之前,我只是尝试在没有异步的情况下测试它 控制器代码 onFileGovernmentIdChange(event) { co

我正在为angular 7应用程序添加一些单元测试,但我不确定如何处理与文件读取器的交互,因为使用async/whenStable、fakeAsync和Promissions无法按预期工作

  • 我正在测试的行为:我需要验证服务
    documentService。上传文件后会调用sendDocument
第一次进近: 在我发现测试FileReader.onload的问题之前,我只是尝试在没有异步的情况下测试它

控制器代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
单元测试规范代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
单元测试结果

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.component.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)
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.component.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.component.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'
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'**
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.component.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)

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.component.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
单元测试规范代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
与第一次进近相同

单元测试结果

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.component.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)
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.component.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.component.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'
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'**
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.component.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)

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.component.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
单元测试结果

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.component.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)
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.component.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.component.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'
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'**
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.component.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)

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.component.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
单元测试分析

正如预期的那样,whenStable代码在方法完成时执行,然而,
onload
方法在结束时继续执行。 通过谷歌搜索,我发现最好将onload部分包装成一个承诺,以确保Angular async跟踪它

第四次进近: 将
onload
包装在承诺中,并验证它是否与
async/whenStable
结构一起工作

控制器代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
单元测试分析

whenStable
函数之后,承诺再次得到解决

第五次进近: 由于
async/whenStable
没有按预期工作,我尝试更改为
fakeAsync/tick/flush
结构

控制器代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
与第四种方法相同(包括承诺中的
onload

单元测试规范代码

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');
}


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

    const commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

    fixture.detectChanges();//Apply onInit changes
    console.log('::: Before calling onFileGovernmentIdChange()');
    component.onFileGovernmentIdChange(customEvent);
    console.log('::: After calling onFileGovernmentIdChange()');
    console.log('::: Before expects... ');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After expects... ');
});
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

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

    console.log('::: After expects... ');
}));
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');
}
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 commerceResponse = new CommerceResponse();
    commerceResponse.commissionPercentage = '11';

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

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

    fixture.detectChanges();
    tick();
    flushMicrotasks();
    flush();
    console.log('::: After Flush Init');
    expect(component.fileGovernmentIdUploaded).toBeTruthy();
    // expect(documentService.sendDocument).toHaveBeenCalledTimes(1);
    console.log('::: After Flush End');
    console.log('::: After expects... ');
}));
单元测试结果

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.component.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)
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.component.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.component.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'
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'**
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.component.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)

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.component.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
LOG:'::在调用onFileGovernmentIdChange()之前
日志:'::Init onfilegovernmendchange'
日志:'::Init saveFileFromInputWithPromise'
日志:':::带有文件的事件…'
日志:'::readFileAsync Init'
日志:“:::结束saveFileFromInputWithPromise”
日志:'::结束OnFileGovernmendChange'
日志:':::调用onFileGovernmentIdChange()后
日志:':::在预期…'之前
日志:':::刷新初始化后'
日志:':::刷新结束后'
日志:':::在预期…'
Chrome 71.0.3578(Mac OS X 10.13.6)文档使用async/fixture.whenStable进行组件测试失败
期望未定义的是真实的。
在UserContext。src/app/documents/documents.component.spec.ts:211:52)
在UserContext。node_modules/zone.js/dist/zone testing.js:1424:1)
在ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
在ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke node_modules/zone.js/dist/zone testing.js:288:1)
日志:'::承诺已在ONLOAD中解析'
日志:'::包含内容的文件','数据:pdf;base64,c3NkZnNkZ2RqZ2hkc2xramdoZGpn'
日志:'::更新前FileGovernmentIDUpload-',未定义
日志:':::更新后FileGovernmentIDUpload-',true
单元测试分析

同样,在fakeAsync/tick/flushMicrotasks/flush结构之后,承诺得到了解决

怎么了? 我遵循我找到的每一个教程和每一种不同的方法,试图在我的测试中包括
FileReader.onload
(因为该方法调用我想要监视和验证的服务),但该方法总是在Angular提供的异步块之后解析。我看到了其他方法,其中对
window.fileReader
进行了模拟,但这不是我测试的目的


有谁能告诉我我的代码或测试方式有什么问题吗?

很好的帖子,完美地描述了我的晚上

fakeAsync不支持FileReader似乎是一个众所周知的问题,因为“它基本上不是一个与计时器相关的异步操作”


看:

这让我发疯了!