运行Angular / Jasmine / Karma,我有一个组件,它使用一个服务来设置一个Observable'itements'数组的值。我使用异步管道显示它。效果很好。
现在,我正在尝试设置一个单元测试并让它通过,但我不确定我是否正确验证'items'数组是否获得了正确的值。
这是相关的组件.html和.ts:
export class ViperDashboardComponent implements OnInit, OnDestroy {
items: Observable<DashboardItem[]>;
constructor(private dashboardService: ViperDashboardService) { }
ngOnInit() {
this.items = this.dashboardService.getDashboardItems();
}
}
<ul class="list-group">
<li class="list-group-item" *ngFor="let item of items | async">
<h3>{{item.value}}</h3>
<p>{{item.detail}}</p>
</li>
</ul>
我的component.spec.ts:
beforeEach(() => {
fixture = TestBed.createComponent(ViperDashboardComponent);
component = fixture.componentInstance;
viperDashboardService =
fixture.debugElement.injector.get(ViperDashboardService);
mockItems = [
{ key: 'item1', value: 'item 1', detail: 'This is item 1' },
{ key: 'item2', value: 'item 2', detail: 'This is item 2' },
{ key: 'item3', value: 'item 3', detail: 'This is item 3' }
];
spy = spyOn(viperDashboardService, 'getDashboardItems')
.and.returnValue(Observable.of<DashboardItem[]>(mockItems));
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call getDashboardItems after component initialzed', () => {
fixture.detectChanges();
expect(spy.calls.any()).toBe(true, 'getDashboardItems should be called');
});
it('should show the dashboard after component initialized', () => {
fixture.detectChanges();
expect(component.items).toEqual(Observable.of(mockItems));
});
具体来说,我想知道:
1)我开始创建一个异步“it”测试,但是当它不起作用时感到惊讶。当我使用异步数据流时,为什么同步测试工作?
2)当我检查component.items与Observable.of(mockItems)的等价性时,我真的测试的是值是否相等?或者我只测试它们都是Observables?有没有更好的办法?
Angular提供了用于测试异步值的实用程序。您可以使用async
utility with the fixture.whenStable
方法或fakeAsync
utility with the tick()
function。然后使用DebugElement
,您实际上可以查询模板以确保正确加载值。
两种测试方法都有效。
使用async
实用程序与whenStable
:
保持你的设置相同,你很高兴去那里。您需要添加一些代码来获取列表的调试元素。在你的beforeEach
:
const list = fixture.debugElement.query(By.css('list-group'));
然后,您可以深入了解该列表并获取单个项目。我不会在如何使用DebugElement
方面走得太远,因为这超出了这个问题的范围。在这里了解更多:https://angular.io/guide/testing#componentfixture-debugelement-and-querybycss。
然后在你的单元测试中:
it('should get the dashboard items when initialized', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => { // wait for your async data
fixture.detectChanges(); // refresh your fake template
/*
now here you can check the debug element for your list
and see that the items in that list correctly represent
your mock data
e.g. expect(listItem1Header.textContent).toEqual('list item 1');
*/
}
}));
使用fakeAsync
实用程序与tick
:
it('should get the dashboard items when initialized', async(() => {
fixture.detectChanges();
tick(); // wait for async data
fixture.detectChanges(); // refresh fake template
/*
now here you can check the debug element for your list
and see that the items in that list correctly represent
your mock data
e.g. expect(listItem1Header.textContent).toEqual('list item 1');
*/
}
}));
总而言之,不要从模板中删除async
管道只是为了使测试更容易。 async
管道是一个很好的实用程序,并为您做了很多清理,加上Angular团队为这个确切的用例提供了一些非常有用的测试工具。希望上述技术之一有效。这听起来像使用DebugElement
和上述实用程序之一将帮助你很多:)
有一个测试可观察量jasmine-marbles
的包
有了它,你可以写这样的测试:
...
spy = spyOn(viperDashboardService, 'getDashboardItems')
.and.returnValue(cold('-r|', { r: mockItems }));
...
it('should show the dashboard after component initialized', () => {
const items$ = cold('-r|', { r: mockItems });
expect(component.items).toBeObservable(items$);
});
但可能它不是最好的例子 - 我通常使用这个包来测试可观察链。例如,如果我在某些带有输入observable的.map()
ing中有服务 - 我可以模拟原始的observable,然后创建一个新的并与服务结果进行比较。
此外,async
和fakeAsync
都不会使用依赖于时间的可观察操作,但是使用jasmine-marbles
,您可以将测试调度程序注入其中,它将像魅力一样工作,没有任何超时 - 立即! Here我有一个如何注入测试调度程序的例子。