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を返すようにしたい.
問題は、非同期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回呼び出されます。
コンポーネントの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()
が呼び出されます。
承認された回答が役に立たない場合に備えて、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の実際のサービスファイルの横にある個別のファイルに置くことを好みます。もちろん、スパイオブジェクトをモックファイルに入れることもできます。