Angular 如何在fromEvent FileReader.loadended之后对组件进行单元测试

Angular 如何在fromEvent FileReader.loadended之后对组件进行单元测试,angular,unit-testing,rxjs,angular-forms,Angular,Unit Testing,Rxjs,Angular Forms,我试图编写一个单元测试,以断言在多步骤表单场景中表单提交后组件中的变量发生了更改。多步骤形式是功能性的,但不可单元测试。我也不知道如何调试它来确定测试失败的根本原因 给定以下功能组件方法: submitFileForm(form: NgForm): void { if (this.uploadFile) { const reader = new FileReader(); fromEvent(reader, 'error') .subscri

我试图编写一个单元测试,以断言在多步骤表单场景中表单提交后组件中的变量发生了更改。多步骤形式是功能性的,但不可单元测试。我也不知道如何调试它来确定测试失败的根本原因

给定以下功能组件方法:

  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);
  }
完整示例位于,其中显示了其他详细信息。这些是角8

我试过了

将读者抽象为自己的服务。 使用NgZone.run和NgZone.runOutsideAngular包装fromEvent。 包装NgZone.run=>{component.step=1;}; 将BehaviorSubject用于表单步骤。 勾选ApplicationRef。 在测试中使用fixture.whenStable。 但我认为这可能是由于对角度或区域的根本误解,以及如何正确注册角度组件的挂起任务。也可能有更好的方法来构造代码,以便以不同的方式进行单元测试。也许读者可以一直在阅读而不是等待表单提交?或者是async/await,但当与RxJS和Angular结合使用时,我对这些模式不太熟悉

我没有尝试注入ChangeDetectionRef,因为我过去在使其可测试方面遇到了麻烦

这里的ngSwitch模式可能不是最干净的,但在我看来它仍然是可能的。就我个人而言,我可能会将其分解为单独的组件,但有时我们需要遵循常见的代码样式

看起来很相似,但在这种情况下,实际的功能方面不起作用,而不是测试。 似乎没有关系。
如今,将其抽象为一项服务似乎是可行的。我可能有一些其他的副作用,但我更喜欢这种模式,它看起来更干净的组成部分。如果我浪费了任何人的时间,我很抱歉。我不会接受这个答案,因为可能有更好的答案来解释我遇到了什么

  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');
        });
      }
    }
测试更改:

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

我认为将FileReader抽象为一个服务,然后模拟可观察的返回将起作用。我不知道为什么在我的实际应用程序中,前几天它不起作用,但基于表单的复杂性,可能会有副作用。
  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);
  }