发布于 2026-01-06 4 阅读
0

使用 RxJS 操作符测试加载状态

使用 RxJS 操作符测试加载状态

一种非常常见的做法是在获取数据时显示某种加载动画。在 Angular 中,我们可以使用 RxJS 的响应式编程方法优雅地实现这一点——但是我们该如何测试它呢?

假设我们要从某个服务获取猫咪名字列表,并且希望在请求发出期间处理加载行为。我们可以这样做:

import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

interface ResponseData<T> {
  data: Array<T>;
}
interface MappedData<T> {
  value: Array<T>;
  isLoading: boolean;
}

@Component({
  selector: 'cat-list',
  template: `
     <ng-container *ngIf="cats$ | async as cats">
        <div class="pending" *ngIf="cats?.isLoading; else loaded"></div>
        <ng-template #loaded>
            <div class="cat" *ngFor="let cat of cats.value">
            <p>Name: {{cat.name}}</p>
            </div>
        </ng-template>
    </ng-container>
`,
  styleUrls: ['./cat.component.less']
})
export class CatListComponent implements OnInit {
  public cats$: Observable<MappedData<Cat>>;

  constructor(private catService: CatService) { }

  ngOnInit() {
    this.cats$ = this.catService.getCats().pipe(
     map((res: ResponseData<Cat>) => {
      return {
        value: res.data,
        isLoading: false
      }
     }),
     startWith({
       value: [],
       isLoading: true
     })
  }
}
Enter fullscreen mode Exit fullscreen mode

我们使用该startWith操作符将可观察对象初始设置为空数组,并将 isLoading 值设置为 true。在单元测试中,我们将确保 UI 能够按预期反映加载状态:

import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';

import { CatListComponent } from './cat-list.component';
import { of, asyncScheduler } from 'rxjs';
import { CatsService } from '../catList/cats.service';

class MockCatsService {
  getCats() {
    return of({
      data: [{
        name: 'Sake',
        age: 10
      },
      {
        name: 'Butter',
        age: 15
      },
      {
        name: 'Parker',
        age: 7
      },
      {
        name: 'Kaylee',
        age: 2
      }]
    }, asyncScheduler);
  }
}

describe('CatListComponent', () => {
  let component: CatListComponent;
  let fixture: ComponentFixture<CatListComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ CatListComponent ],
      providers: [{
        provide: CatsService,
        useClass: MockCatsService
      }],
    })
    .compileComponents();
  }));

  it('should create', () => {
    const fixture = TestBed.createComponent(CatListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    expect(component).toBeTruthy();
    fixture.destroy();
  });

  it('should show loading div while results are loading', fakeAsync((): void => {
    const fixture = TestBed.createComponent(CatListComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    const loadingDiv = compiled.querySelector('.loading');
    expect(loadingDiv).toBeTruthy();
    fixture.destroy();
  }));

  it('should show cat divs when results have loaded', fakeAsync((): void => {
    const fixture = TestBed.createComponent(CatListComponent);
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    const loadingDiv = compiled.getElementsByClassName('cat');
    expect(loadingDiv.length).toBe(4);
    fixture.destroy();
  }));
});

Enter fullscreen mode Exit fullscreen mode

因为我首先想测试isLoading状态,所以希望在执行方法之前能够看到用户界面的样子getCats,因此我将断言封装在一个fakeAsync函数中。这个函数创建了一个模拟的异步区域,我可以在其中调用一个tick函数来模拟时间的流逝。通过这种方式,我实际上可以像测试同步对象一样测试我的 Observable。

我为每个“计时器”调用tick` fixture.detectChangestimer`,以触发组件生命周期,例如ngOnInit,当可观察对象创建时,当使用视图中的异步管道订阅可观察对象时等等。

文章来源:https://dev.to/bitovi/testing-loading-states-using-rxjs-operators-2o9d