[英]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:我的尝试:
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
的测试用例中几乎每隔一行(如此极端)都添加了这一行,但没有任何改变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 中也不起作用)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)console.log()
行出现时呈现)setTimeout()
, with same reasoningsetTimeout()
,理由相同My other thoughts are also:我的其他想法也是:
declarations
, imports
, etc.?declarations
、 imports
等方面的内容?async
pipes (although they work for subscribe()
) 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
.对我来说奇怪的是,您同时拥有
store
和mockStore
的句柄。
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.