简体   繁体   English

Angular 异步 pipe 在单元测试中未正确更新

[英]Angular Async pipe does not properly update in Unit Tests

I'm facing an issue with rendering updating Observables through async pipe in HTML while writing unit tests.在编写单元测试时,我遇到了通过 HTML 中的async pipe 渲染更新 Observables 的问题。

The idea is that I want test not just the component but whether child components are both rendered and have correct Inputs.这个想法是,我不仅要测试组件,还要测试子组件是否都被渲染并具有正确的输入。

This is the minimal example that the issue occurs:这是发生问题的最小示例:

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

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

The minimal example of 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
  }
}

Unit test for this module/component:此模块/组件的单元测试:

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

While real code and console.log(plans);而真正的代码和console.log(plans); in unit test show correct data, for some reason the plans$ | async在单元测试中显示正确的数据,出于某种原因plans$ | async plans$ | async in HTML always has default state. plans$ | async中的异步始终具有默认 state。 The issue is solely related to HTML.该问题仅与 HTML 有关。

My attempted tries:我的尝试:

  1. Add fixture.detectChanges();添加fixture.detectChanges(); - Added this line to almost every second line (to such extreme) in both beforeEach() and in it test case but nothing was changed - 在beforeEach()it的测试用例中几乎每隔一行(如此极端)都添加了这一行,但没有任何改变
  2. Hardcoded with component.plans$ = of([ { name: 'name' } as any ]);component.plans$ = of([ { name: 'name' } as any ]);硬编码in it test case (I was wondering if this had something to do with Store/MockStore but even hardcoded value appears to be not working in HTML)it的测试用例中(我想知道这是否与 Store/MockStore 有关,但即使是硬编码的值似乎在 HTML 中也不起作用)
  3. Use fixture.whenRenderingDone().then(async () => { <code> });使用fixture.whenRenderingDone().then(async () => { <code> }); in entire test case (perhaps HTML was not rendered by the time console.log() lines came up)在整个测试用例中(也许 HTML 没有在console.log()行出现时呈现)
  4. Similar to the third, I also tried with setTimeout() , with same reasoning与第三个类似,我也尝试了setTimeout() ,理由相同

My other thoughts are also:我的其他想法也是:

  1. I have missed something in declarations , imports , etc.?我错过了declarationsimports等方面的内容?
  2. MockStore/Store does not properly trigger changes to async pipes (although they work for subscribe() ) MockStore/Store 没有正确触发对async管道的更改(尽管它们适用于subscribe()

If something is missing, let me know.如果有什么遗漏,请告诉我。 Thank you in advance.先感谢您。

What is strange to me is that you have a handle on both store and mockStore .对我来说奇怪的是,您同时拥有storemockStore的句柄。

I think you should only use one.我认为你应该只使用一个。 I don't have much experience with mockStore so I will try the actual store.我对 mockStore 没有太多经验,所以我会尝试实际的商店。 Try doing integration testing as shown here .尝试按照此处所示进行integration testing With integration testing we have the actual store and not a mock store.通过集成测试,我们有实际的商店而不是模拟商店。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM