Angular测试如何防止ngOnInit调用直接测试方法 [英] Angular testing how to prevent ngOnInit call to test a method directly

查看:115
本文介绍了Angular测试如何防止ngOnInit调用直接测试方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a component. Inside of it, the ngOnInit function calls another function of component to retrieve user List. I want to make two series of tets:


  • 首先测试ngOnInit是否被正确触发并填充用户列表

  • 第二次我想测试我的刷新函数,它也调用getUserList()

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

The first test, with ngOnInit trigger, when I call fixture.detectChanges() works properly.

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

My problem is when testing the refresh function: as soon as I call fixture.detectChanges(), ngOnInit is triggered and then I am unable to know where my results come from and if my refresh() function will be tested properly.

有没有办法,在我对 refresh()方法的第二系列测试之前,删除或阻止 ngOnInit()所以它没有被调用 fixture.detectChanges()

Is there any way, before my second series of tests on refresh() method, to "delete" or "block" the ngOnInit() so it's not called on fixture.detectChanges()?

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

I tried to look at overrideComponent but it seems it doesn't allow to delete ngOnInit().

或者除了在我的情况下使用 fixture.detectChanges 之外,还有什么方法可以检测到其他变化吗? / strong>

Or is there any way to detect changes other than using fixture.detectChanges in my case?

以下是组件,存根服务和我的规范文件的代码。

Here is the code for component, stub service and my spec files.

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



组件规格文件



Component spec file

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 service (if needed)

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



我的试用



我尝试了一些解决方法,但我发现它有点......冗长而且可能有点过分!

My trials

I tried some "workaround" but I found it to be a little.... verbose and maybe overkill!

例如:

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


推荐答案

防止生命周期挂钩( ngOnInit )被调用是一个错误的方向。这个问题有两个可能的原因。要么测试不够孤立,要么测试策略是错误的。

Preventing lifecycle hook (ngOnInit) from being called is a wrong direction. The problem has two possible causes. Either the test isn't isolated enough, or testing strategy is wrong.

角度指南非常对测试隔离的具体和见解


然而,使用不依赖于Angular的独立单元测试来探索应用程序类的内部逻辑通常会更有效率。这些测试通常更小,更易于阅读,编写和维护。

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.

因此,隔离测试应该实例化一个类并测试其方法

So isolated tests just should instantiate a class and test its methods

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测试视为功能测试。一个好的测试套件可以包含两者。

Isolated tests have a shortcoming - they don't test DI, while TestBed tests do. Depending on the point of view and testing strategy, isolated tests can be considered unit tests, and TestBed tests can be considered functional tests. And a good test suite can contain both.

在上面的代码中应该通过刷新功能获取用户列表 test显然是一个功能测试,它将组件实例视为黑盒子。

In the code above should get the user List via refresh function test is obviously a functional test, it treats component instance as a blackbox.

可以添加几个TestBed单元测试来填补空白,它们可能足够坚固,不会受到隔离测试的困扰(尽管后者肯定更精确) :

A couple of TestBed unit tests can be added to fill the gap, they probably will be solid enough to not bother with isolated tests (although the latter are surely more precise):

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测试如何防止ngOnInit调用直接测试方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆