web-dev-qa-db-ja.com

angular2ユニットテスト:未定義のcomponentInstance.method()のプロパティを読み取れません

mycomponent.spec.tsクラス:

これはエラーをスローします:未定義のプロパティ '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()がスペッククラスのmyService.getData()メソッドを呼び出したときにmyService.getDataがDATA_OBJECTを返すようにしたい.

5
DaenKhaleesi

問題は、非同期beforeEachが正しく実装されておらず、これにより競合状態が発生することです。

beforeEachブロックで.compileComponents().then(() => { ... })を実行すると、少なくとも1ティックの間、thenコールバックでのコード実行が遅延します。 itブロックは、割り当てられる前に決して待機せず、myComponent変数にアクセスします。

この種の競合状態は、テストが失敗しない場合、あまり明白ではなくなり、より危険になる可能性があります。代わりに、以前のテストのbeforeEachが現在のテストの変数に影響を与えると、テストが相互汚染される可能性があります。

.compileComponents()は、styleUrlsおよびtemplateUrl(上記の場合のように)を持つコンポーネントがない限り、同期です。この場合は非同期になり、asyncヘルパーを使用する必要があります。

_// asynchronous block
beforeEach(async(() => {    
  TestBed.configureTestingModule({ ... })
  .compileComponents();
}));

// synchronous block
beforeEach(() => {    
  myComponent = ...
});
_

経験則として、ブロックが非同期である可能性がある場合、ブロックはasync of fakeAsyncヘルパーでラップする必要があります。

コンポーネントクラスがTestBedでテストされると、それらはライフサイクルに従い、フックが自動的に呼び出されます。手動でngOnInit()を呼び出す必要はなく(別の回答で説明されています)、フックが2回呼び出されます。

4
Estus Flask

コンポーネントのinit()を実行するためにngOnInit()を手動で呼び出す必要はありません。

コードを以下のコードに変更します

_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()が呼び出されます。

4
Amit Chigadani

承認された回答が役に立たない場合に備えて、NgZoneが適切にモックされていない場合にも、このエラーが発生する可能性があります。私は基礎となるメカニズムを理解していませんが、以前は次のようなオブジェクトリテラルとしてモックされた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]
})

これを行うには他の方法があります(そして一般にサービスをモックするために)言及する価値があります。以下にいくつかの例を示します NgZone依存関係のあるコンポーネントのjasmineテストの実行 。 Jasmine Spy Objectを作成することはかなり便利ですが、私は個人的にモックをDRYの実際のサービスファイルの横にある個別のファイルに置くことを好みます。もちろん、スパイオブジェクトをモックファイルに入れることもできます。

0
Aleksi