我有一个组件.在其中,ngOnInit函数调用组件的另一个函数来检索用户List.我想制作两个系列的小提琴:
>首先测试ngOnInit是否被正确触发并填充用户列表
>第二次我想测试我的刷新函数,它也调用getUserList()
当我调用fixture.detectChanges()时,使用ngOnInit触发器的第一个测试正常工作.
问题
我的问题是在测试刷新功能时:一旦我调用fixture.detectChanges(),就会触发ngOnInit,然后我无法知道结果的来源以及我的refresh()函数是否会被正确测试.
在我对refresh()方法的第二系列测试之前,有没有办法“删除”或“阻止”ngOnInit()所以它没有在fixture.detectChanges()上调用?
我试着看看overrideComponent,但它似乎不允许删除ngOnInit().
或者除了在我的情况下使用fixture.detectChanges之外,有没有办法检测更改?
码
零件
import { Component,OnInit,ViewContainerRef } from '@angular/core'; import { UserManagementService } from '../../shared/services/global.api'; import { UserListItemComponent } from './user-list-item.component'; @Component({ selector: 'app-user-list',templateUrl: './user-list.component.html' }) export class UserListComponent implements OnInit { public userList = []; constructor( private _userManagementService: UserManagementService,) { } ngOnInit() { this.getUserList(); } onRefreshUserList() { this.getUserList(); } getUserList(notifyWhenComplete = false) { this._userManagementService.getListUsers().subscribe( result => { this.userList = result.objects; },error => { console.error(error); },() => { if (notifyWhenComplete) { console.info('Notification'); } } ); } }
组件规范文件
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { async,fakeAsync,ComponentFixture,TestBed,tick,inject } from '@angular/core/testing'; import { Observable } from 'rxjs/Observable'; // Components import { UserListComponent } from './user-list.component'; // Services import { UserManagementService } from '../../shared/services/global.api'; import { UserManagementServiceStub } from '../../testing/services/global.api.stub'; let comp: UserListComponent; let fixture: ComponentFixture<UserListComponent>; let service: UserManagementService; describe('UserListComponent',() => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [UserListComponent],imports: [],providers: [ { provide: UserManagementService,useClass: UserManagementServiceStub } ],schemas: [ NO_ERRORS_SCHEMA ] }) .compileComponents(); })); tests(); }); function tests() { beforeEach(() => { fixture = TestBed.createComponent(UserListComponent); comp = fixture.componentInstance; service = TestBed.get(UserManagementService); }); it(`should be initialized`,() => { expect(fixture).toBeDefined(); expect(comp).toBeDefined(); }); it(`should NOT have any user in list before ngOnInit`,() => { expect(comp.userList.length).toBe(0,'user list is empty before init'); }); it(`should get the user List after ngOnInit`,async(() => { fixture.detectChanges(); // This triggers the ngOnInit and thus the getUserList() method // Works perfectly. ngOnInit was triggered and my list is OK expect(comp.userList.length).toBe(3,'user list exists after init'); })); it(`should get the user List via refresh function`,fakeAsync(() => { comp.onRefreshUserList(); // Can be commented,the test will pass because of ngOnInit trigger tick(); // This triggers the ngOnInit which ALSO call getUserList() // so my result can come from getUserList() method called from both source: onRefreshUserList() AND through ngOnInit(). fixture.detectChanges(); // If I comment the first line,the expectation is met because ngOnInit was triggered! expect(comp.userList.length).toBe(3,'user list after function call'); })); }
存根服务(如果需要)
import { Observable } from 'rxjs/Observable'; export class UserManagementServiceStub { getListUsers() { return Observable.from([ { count: 3,objects: [ { id: "7f5a6610-f59b-4cd7-b649-1ea3cf72347f",name: "user 1",group: "any" },{ id: "d6f54c29-810e-43d8-8083-0712d1c412a3",name: "user 2",{ id: "2874f506-009a-4af8-8ca5-f6e6ba1824cb",name: "user 3",group: "any" } ] } ]); } }
我的考验
我尝试了一些“解决方法”,但我发现它有点……冗长,也许是矫枉过正!
例如:
it(`should get the user List via refresh function`,fakeAsync(() => { expect(comp.userList.length).toBe(0,'user list must be empty'); // Here ngOnInit is called,so I override the result from onInit fixture.detectChanges(); expect(comp.userList.length).toBe(3,'ngOnInit'); comp.userList = []; fixture.detectChanges(); expect(comp.userList.length).toBe(0,'ngOnInit'); // Then call the refresh function comp.onRefreshUserList(true); tick(); fixture.detectChanges(); expect(comp.userList.length).toBe(3,'user list after function call'); }));
角度指南相当于specific and opinionated on test isolation:
However,it’s often more productive to explore the inner logic of application classes with isolated unit tests that don’t depend upon Angular. Such tests are often smaller and easier to read,write,and maintain.
所以隔离测试应该实例化一个类并测试它的方法
userManagementService = new UserManagementServiceStub; comp = new UserListComponent(userManagementService); spyOn(comp,'getUserList'); ... comp.ngOnInit(); expect(comp.getUserList).toHaveBeenCalled(); ... comp.onRefreshUserList(); expect(comp.getUserList).toHaveBeenCalled();
隔离测试有一个缺点 – 它们不测试DI,而TestBed测试则测试DI.根据观点和测试策略,可以将隔离测试视为单元测试,并将TestBed测试视为功能测试.一个好的测试套件可以包含两者.
在上面的代码中应该通过刷新功能测试得到用户列表显然是一个功能测试,它将组件实例视为黑盒子.
可以添加几个TestBed单元测试来填补空白,它们可能足够坚固,不会受到隔离测试的困扰(尽管后者肯定更精确):
spyOn(comp,'getUserList'); comp.onRefreshUserList(); expect(comp.getUserList).toHaveBeenCalledTimes(1); ... spyOn(comp,'getUserList'); spyOn(comp,'ngOnInit').and.callThrough(); tick(); fixture.detectChanges(); expect(comp.ngOnInit).toHaveBeenCalled(); expect(comp.getUserList).toHaveBeenCalledTimes(1);