angular2单元测试:无法读取componentInstance.method()未定义的属性

时间:2017-05-15 06:56:02

标签: angular unit-testing typescript jasmine karma-jasmine

mycomponent.spec.ts class:

这会引发错误:无法读取属性  ngOnInit'未定义的。

let myComponent: MyComponent;
let myService: MyService;

describe('myComponent', () => {
   beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [MyComponent],
    providers: [
      {provide: MyService, useClass: MockMyService} // **--passing Mock service**
  ]
}).compileComponents()
  .then(() => {
    myComponent = TestBed.createComponent(MyComponent).componentInstance;
    myService = TestBed.get(MyService);
    console.log(myService.getData());
  });
});

it('should get the mock data', () => {
   myComponent.ngOnInit(); //-----------> seems like myComponent is not defined at this place, how to resolve this error 
   expect(myComponent.data).toBe(DATA_OBJECT);
  });
});

下面是MyComponent:

@Component({
  selector: 'pm-catalogs',
  templateUrl: './catalog-list.component.html'
})

export class MyComponent implements OnInit {

 public data: IData[];

 constructor(private _myService: MyService) {

}

public ngOnInit(): void {
this._myService.getData()
  .subscribe(
    data => this.data = data
  //  error => this.errorMessage = <any>error
  );
 }
}

下面是模拟服务

  export const DATA_OBJECT: IData[] = [
 {
   'Id': 1,
   'value': 'abc'
 },
 {
'Id': 2,
'value': 'xyz'
 }];

@Injectable()
export class MockMyService {
 public getData(): Observable<IData[]> {
    return Observable.of(DATA_OBJECT);
  }
}

我是Angular2测试的新手,当myComponent.ngOnInit()在我的spec类中调用myService.getData()方法时,我希望myService.getData返回DATA_OBJECT 请帮助我实现这一目标。

3 个答案:

答案 0 :(得分:4)

您不必手动调用ngOnInit()来运行组件的init()。

将代码修改为以下代码

let myComponent: MyComponent;
let myService: MyService;
let fixture;

describe('myComponent', () => {
   beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [MyComponent],
    providers: [
      {provide: MyService, useClass: MockMyService} // **--passing Mock service**
  ]
}).compileComponents()
  .then(() => {
   fixture = TestBed.createComponent(MyComponent);
    myComponent = TestBed.createComponent(MyComponent).componentInstance;
    myService = TestBed.get(MyService);
    console.log(myService.getData());
  });
});

it('should get the mock data', () => {
   fixture.detectChanges();  // this line will call components ngOnInit() method
   expect(myComponent.data).toBe(DATA_OBJECT);
  });
})

查看行fixture.detectChanges();第一次发生更改检测时,将调用组件ngOnInit()

答案 1 :(得分:3)

问题是异步$group->each(function($item){ array_push($bounceZoneList,$item->**the_entry_you_want_to_be_pushed_to_the_array**); } 未正确实现,这会导致竞争条件。

beforeEach阻止中执行.compileComponents().then(() => { ... })会导致beforeEach回调中的代码执行延迟至少一次。 then块永远不会等待并在有机会被分配之前访问it变量。

当测试没有失败时,这种竞争条件会变得不那么明显,也会变得更加危险。相反,当先前测试的myComponent影响当前测试中的变量时,测试可能会被交叉污染。

beforeEach是同步的,除非有.compileComponents()styleUrls的组件(如上例所示)。在这种情况下,它变为异步,并且应该使用templateUrl帮助程序:

async

根据经验,如果块可能是异步的,则块应该使用// asynchronous block beforeEach(async(() => { TestBed.configureTestingModule({ ... }) .compileComponents(); })); // synchronous block beforeEach(() => { myComponent = ... }); async帮助程序包装。

使用fakeAsync测试组件类时,它们遵循生命周期并自动调用它们的挂钩。不需要手动调用TestBed(正如另一个答案所解释的那样)并且将导致调用挂钩两次。

答案 2 :(得分:0)

以防万一某个人认为接受的答案没有帮助,并且如果NgZone没有得到适当的嘲笑,此错误也可能出现。我不了解底层机制,但是a以前提供了模拟NgZone作为对象文字,如下所示:

TestBed.configureTestingModule({
    providers: [{
        provide: NgZone,
        useValue: {
            runOutsideAngular: (fn: (...args: Array<any>) => T) => { return fn(); }
            ... other NgZone functions ...
        }
    }],
    declarations: [MyComponent]
})

由于某些原因,该方法在某些测试中有效,因此我起初并不怀疑,但过了一会儿,我创建了一个模拟的NgZone类,该类扩展了实际的NgZone

export class NgZoneMock extends NgZone {
    constructor() {
        super({ enableLongStackTrace: false });
    }

    public runOutsideAngular<T>(fn: (...args: Array<any>) => T) { return fn(); }
    public run<T>(fn: (...args: Array<any>) => T, applyThis?: any, applyArgs?: Array<any> | undefined) { return fn(); }
    public runTask<T>(fn: (...args: Array<any>) => T, applyThis?: any, applyArgs?: Array<any> | undefined) { return fn(); }
    public runGuarded<T>(fn: (...args: Array<any>) => T, applyThis?: any, applyArgs?: Array<any> | undefined) { return fn(); }
    public onUnstable = new EventEmitter<any>();
    public onStable = new EventEmitter<any>();
    public onMicrotaskEmpty = new EventEmitter<any>();
    public onError = new EventEmitter<any>();
}

然后仅使用TestBed配置中的类:

TestBed.configureTestingModule({
    providers: [{
        provide: NgZone,
        useClass: NgZoneMock
    }],
    declarations: [MyComponent]
})

值得一提的是,还有其他方法可以做到这一点(并且通常可以模拟任何服务)。以下是一些示例Running jasmine tests for a component with NgZone dependency。创建一个Jasmine Spy对象非常有用,但是我个人更喜欢将模拟放置在DRY实际服务文件旁边的单独文件中。当然,您也可以将“间谍对象”放入模拟文件中。