Her*_*njo 7 javascript filereader jasmine karma-jasmine angular
我正在向我的 Angular 7 应用程序添加一些单元测试,但我不确定如何处理与 FileReader 的交互,因为使用 async/whenStable、fakeAsync 和 Promise 不能按预期工作。
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');
}
Run Code Online (Sandbox Code Playgroud)
单元测试规范代码
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... ');
});
Run Code Online (Sandbox Code Playgroud)
单元测试结果
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)
Run Code Online (Sandbox Code Playgroud)
单元测试分析
方法内的代码onload永远不会被执行
在没有观察者的情况下测试 FileReader.onload:为了遵循工作流程,我删除了内部的观察者onload以验证这是否可能是问题所在。
控制器代码
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');
}
Run Code Online (Sandbox Code Playgroud)
单元测试规范代码
与第一种方法相同
单元测试结果
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'
Run Code Online (Sandbox Code Playgroud)
单元测试分析
现在方法中的代码onload已执行,但看起来像异步操作,因为它是在最后执行的。
使用异步单元测试测试 FileReader.onload:当我发现某种异步操作时,我包含了async/fixture.whenStable角度组合来等待异步代码完成。
控制器代码
与第二种方法相同
单元测试规范代码
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... ');
}));
Run Code Online (Sandbox Code Playgroud)
单元测试结果
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'**
Run Code Online (Sandbox Code Playgroud)
单元测试分析
正如预期的那样,whenStable 代码在该方法完成时执行,但是该onload方法在最后继续执行。谷歌搜索我发现最好将 onload 部分包装在 Promise 中,以确保它被 Angular 异步跟踪。
将其包装onload在一个承诺中并验证它是否适用于该async/whenStable结构。
控制器代码
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');
}
Run Code Online (Sandbox Code Playgroud)
单元测试规范代码
与第三种方法相同
单元测试结果
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)
Run Code Online (Sandbox Code Playgroud)
单元测试分析
同样,Promise 在whenStable函数之后得到解决。
由于async/whenStable没有按预期工作,我尝试更改fakeAsync/tick/flush结构。
控制器代码
与第四种方法相同(包括onload在承诺中)
单元测试规范代码
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... ');
}));
Run Code Online (Sandbox Code Playgroud)
单元测试结果
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
Run Code Online (Sandbox Code Playgroud)
单元测试分析
同样,Promise 在 fakeAsync/tick/flushMicrotasks/flush 结构之后被解析
我遵循了我发现的每个教程以及尝试将其包含FileReader.onload在我的测试中的每个不同方法(因为该方法调用我想要监视和验证的服务),但始终在 Angular 提供的异步块之后解析该方法。我看到其他方法被window.fileReader嘲笑,但这不是我测试的目的。
那么有人可以告诉我我的代码或我测试的方式有什么问题吗?
小智 4
很棒的帖子,完美地描述了我的夜晚。
fakeAsync 不支持 FileReader 似乎是一个已知问题,因为“它本质上不是与计时器相关的异步操作”。
请参阅: https: //github.com/angular/zone.js/issues/1120