【问题标题】:Angular Async pipe does not properly update in Unit TestsAngular Async 管道在单元测试中没有正确更新
【发布时间】:2021-06-03 14:59:55
【问题描述】:

在编写单元测试时,我遇到了通过 HTML 中的async 管道渲染更新 Observables 的问题。

我的想法是,我不仅要测试组件,还要测试子组件是否都被渲染并具有正确的输入。

这是发生问题的最小示例:

<ng-container *ngFor="let plan of plans$ | async">
  <child-component [plan]="plan"></child-component>
</ng-container>

Visible plans: {{ plans$ | async | json }}

Component 的最小例子:

export class RecommendationsComponent implements OnInit {
  public plans$: Observable<Plan[]>;

  constructor(private readonly _store: Store<State>) {
    this.plans$ = this._store.pipe(select(selectRecommendationsPayload));
  }

  public ngOnInit(): void {
    this.getRecommendations(); // Action dispatch, state is filled with data
  }
}

此模块/组件的单元测试:

describe('Recommendations', () => {
  let component: RecommendationsComponent;
  let fixture: ComponentFixture<RecommendationsComponent>;
  let store: Store<any>;
  let mockStore: MockStore<any>;
  let actions$: ReplaySubject<any> = new ReplaySubject<any>();

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [RecommendationsComponent],
      imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule],
      providers: [
        MockStore,
        provideMockStore({ initialState: initialStateMock }),
        provideMockActions(() => actions$),
      ],
    });

    store = TestBed.inject(Store);
    mockStore = TestBed.inject(MockStore);

    fixture = TestBed.createComponent(RecommendationsComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should successfully retrieve and handle plans', () => {
    recommendationsService.getRecommendations = jasmine.createSpy().and.returnValue(of(plans)); // Mock BE response with non-empty data

    component.plans$.subscribe(plans => {
      console.log(plans);
      console.log(fixture.debugElement);
      // A few expect() based on state and HTML...
      // This fires since all logic starts on ngOnInit() lifecycle
    });
  });
});

虽然单元测试中的真实代码和console.log(plans); 显示正确的数据,但出于某种原因,HTML 中的plans$ | async 始终具有默认状态。该问题仅与 HTML 有关。

我的尝试:

  1. 添加fixture.detectChanges(); - 在beforeEach()it 测试用例中几乎每隔一行(如此极端)都添加了这一行,但没有任何改变
  2. it 测试用例中使用 component.plans$ = of([ { name: 'name' } as any ]); 硬编码(我想知道这是否与 Store/MockStore 有关,但即使是硬编码的值似乎在 HTML 中也不起作用)
  3. 在整个测试用例中使用 fixture.whenRenderingDone().then(async () =&gt; { &lt;code&gt; });(可能在出现 console.log() 行时 HTML 尚未呈现)
  4. 和第三个类似,我也试过setTimeout(),道理一样

我的其他想法也是:

  1. 我错过了declarationsimports 等中的某些内容?
  2. MockStore/Store 无法正确触发对 async 管道的更改(尽管它们适用于 subscribe()

如果有什么遗漏,请告诉我。提前谢谢你。

【问题讨论】:

    标签: javascript angular unit-testing jasmine


    【解决方案1】:

    让我感到奇怪的是,您同时拥有 storemockStore

    我认为你应该只使用一个。我对 mockStore 没有太多经验,所以我会尝试实际的商店。尝试执行integration testing,如图所示here。通过集成测试,我们有实际的商店,而不是模拟商店。

    describe('Recommendations', () => {
      let component: RecommendationsComponent;
      let fixture: ComponentFixture<RecommendationsComponent>;
      let store: Store<any>;
      let actions$: ReplaySubject<any> = new ReplaySubject<any>();
    
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          declarations: [RecommendationsComponent],
          imports: [
             RouterTestingModule.withRoutes([]), 
             HttpClientTestingModule,
             StoreModule.forRoot({
               // Pay attention here, make sure this is provided in a way
               // where your selectors will work (make sure the structure is
               // good)
               recommendations: recommendationsReducer,
             })
          ],
        });
    
        store = TestBed.inject(Store);
        // load the recommendations into the store by dispatching
        store.dispatch(new loadRecommendations([]));
        fixture = TestBed.createComponent(RecommendationsComponent);
        component = fixture.componentInstance;
        // see your state here, make sure the selector works
        store.subscribe(state => console.log(state));
        // any time you want to change plans, do another dispatch
        store.dispatch(new loadRecommendations([/* add stuff here */]));
        // the following above should make plans$ emit every time
        fixture.detectChanges();
      });
      
    
     // !! -- The rest is up to you from now on but what I presented above
     // should help in getting new plans$ with the async pipe !!-
    
      it('should successfully retrieve and handle plans', () => {
        recommendationsService.getRecommendations = jasmine.createSpy().and.returnValue(of(plans)); // Mock BE response with non-empty data
    
        component.plans$.subscribe(plans => {
          console.log(plans);
          console.log(fixture.debugElement);
          // A few expect() based on state and HTML...
          // This fires since all logic starts on ngOnInit() lifecycle
        });
      });
    });
    

    【讨论】:

    • 嗯,似乎对我不起作用 (Error: Invalid provider for the NgModule 'DynamicTestModule' - only instances of Provider and Type are allowed)。我正在使用 2 个不同的商店,因为在某些情况下效果或动作都不会被触发。我可能走错路了。感谢您提供代码和链接,我会尝试更新并测试是否可以修复。
    • 你不应该提供效果,只提供 StoreModue.forRoot 中的 reducer。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 2017-05-23
    • 2019-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-11
    相关资源
    最近更新 更多