angular - 如何在 fromEvent FileReader.loadended 之后对组件进行单元测试
问题描述
我正在尝试编写一个单元测试来断言组件中的变量在多步骤表单场景中的表单提交后发生了变化。多步骤形式具有功能性,但不可单元测试。我也不确定如何调试它以确定测试失败的根本原因。
给定以下功能组件方法:
submitFileForm(form: NgForm): void {
if (this.uploadFile) {
const reader = new FileReader();
fromEvent(reader, 'error')
.subscribe((e: ProgressEvent) => {
console.error(e);
reader.abort();
});
fromEvent(reader, 'loadend')
.pipe(map(() => btoa(reader.result as string)))
.subscribe(() => {
// This works, but not in the test.
component.step = 1;
}, (err) => {
console.error(err);
this.step = -1;
});
reader.readAsText(this.uploadFile, 'UTF-8');
}
}
以及以下测试:
it('should go from step 0 to step 1', () => {
const mockBlob: any = new Blob([
`A,B,C\naaaa,bbb,ccc`,
], { type: 'text/csv' });
mockBlob.name = 'my.csv';
const mockFile = mockBlob as File;
component.uploadFile = mockFile;
fixture.detectChanges();
component.submitFileForm(component.fileForm);
fixture.detectChanges();
// This fails. component.step is 0. After the test is finished step is 1.
expect(component.step).toEqual(1);
}
完整示例位于https://stackblitz.com/edit/angular-zr3nej和https://angular-zr3nej.stackblitz.io/,其中显示了更多详细信息。这些在 Angular 8 中。
我试过了
- 将读者抽象为自己的服务。
NgZone.run
使用and包装 fromEventNgZone.runOutsideAngular
。- 包装
NgZone.run(() => { component.step = 1; });
- 为表单步骤使用 BehaviorSubject。
- 勾选 ApplicationRef。
fixture.whenStable()
在测试中使用。
但我认为这可能是由于对 Angular 或 Zone 的根本误解,以及如何正确注册 Angular 组件的待处理任务。还可能有更好的方法来构建代码,以便以不同的方式进行单元测试。也许读者可以一直在阅读而不是等待表单提交?或者可能是 async/await,但是当我与 RxJS 和 Angular 结合使用时,我对这些模式并不熟悉。
我没有尝试过注入ChangeDetectionRef
,因为过去我在使该可测试时遇到了麻烦。
这里的 ngSwitch 模式可能不是最干净的,但我认为它仍然是可能的。就我个人而言,我可能会将其分解为单独的组件,但有时我们需要遵守通用的代码样式。
- Observable fromEvent do not activate detectionchange on angular似乎是相似的,但在这种情况下,实际功能方面不起作用,而不是测试。
- 单元测试 fromEvent observable withLatestFrom似乎不相关。
解决方案
抽象到服务似乎在今天起作用。我可能还有其他一些副作用,但我确实更喜欢这种模式,并且它在组件中看起来更干净。如果我浪费了任何人的时间,我很抱歉。我不会接受这个答案,因为可能有更好的答案可以解释我遇到的问题。
export class FileReaderService {
read(file: File): Observable<string> {
return Observable.create((observer: Observer<string>) => {
const reader = new FileReader();
reader.onerror = () => {
reader.abort();
observer.error('An error occurred reading the file.');
};
reader.onloadend = () => {
observer.next(reader.result as string);
observer.complete();
};
reader.readAsText(file, 'UTF-8');
});
}
}
组件变化:
submitFileForm(form: NgForm): void {
if (this.uploadFile) {
this.fileReaderService.read(this.uploadFile)
.subscribe(() => {
component.step = 1;
}, (err) => {
console.error(err);
this.step = -1;
});
}
}
测试变化:
it('should go from step 0 to step 1', () => {
const contents = `A,B,C\naaaa,bbb,ccc`;
const reader = fixture.debugElement.injector.get(FileReaderService);
spyOn(reader, 'read').and.returnValue(of(contents));
const mockBlob: any = new Blob([contents]), { type: 'text/csv' });
mockBlob.name = 'my.csv';
const mockFile = mockBlob as File;
component.uploadFile = mockFile;
fixture.detectChanges();
component.submitFileForm(component.fileForm);
fixture.detectChanges();
expect(component.step).toEqual(1);
}