gpt4 book ai didi

Angular Testing 如何防止 ngOnInit 调用直接测试方法

转载 作者:太空狗 更新时间:2023-10-29 16:54:22 25 4
gpt4 key购买 nike

上下文

我有一个组件。在其中,ngOnInit 函数调用组件的另一个函数来检索用户列表。我想制作两个系列的四 Angular :

  • 首先测试 ngOnInit 是否正确触发并填充用户列表
  • 第二次我想测试我的刷新函数,它也调用 getUserList()

第一个测试,使用 ngOnInit 触发器,当我调用 fixture.detectChanges() 时正常工作。

问题

我的问题是在测试刷新功能时:只要我调用 fixture.detectChanges(),ngOnInit 就会被触发,然后我无法知道我的结果来自哪里以及我的 refresh() 功能是否会被正确测试。

在我对 refresh() 方法进行第二系列测试之前,有什么方法可以“删除”或“阻止”ngOnInit(),这样它不是在 fixture.detectChanges() 上调用的吗?

我试图查看 overrideComponent,但它似乎不允许删除 ngOnInit()

或者除了在我的案例中使用 fixture.detectChanges 之外,还有什么方法可以检测变化吗?

代码

这是组件、 stub 服务和我的规范文件的代码。

组件

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

stub 服务(如果需要)

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",
group: "any"
},
{
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');
}));

最佳答案

阻止生命周期钩子(Hook)(ngOnInit)被调用是一个错误的方向。该问题有两个可能的原因。要么测试不够隔离,要么测试策略错误。

Angular 指南相当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 测试会。根据观点和测试策略的不同,隔离测试可以被认为是单元测试,TestBed 测试可以被认为是功能测试。一个好的测试套件可以包含两者。

在上面的代码中,should get the user List via refresh function 测试显然是一个功能测试,它将组件实例视为一个黑盒。

可以添加几个 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);

关于 Angular Testing 如何防止 ngOnInit 调用直接测试方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43306856/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com