mycomponent.spec.ts class:
This throws error: Cannot read property 'ngOnInit' of undefined.
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);
});
});
below is 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
);
}
}
below is mock service
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);
}
}
I am newbie to Angular2 testing and I want myService.getData to return DATA_OBJECT when myComponent.ngOnInit() calls myService.getData() method in my spec class Please help me to achieve that.
The problem is that asynchronous beforeEach
is incorrectly implemented, this results in race condition.
Doing .compileComponents().then(() => { ... })
in beforeEach
block results in delaying code execution in then
callback at least for one tick. it
block never waits and accesses myComponent
variable before it had a chance to be assigned.
This kind of race conditions can become less obvious and more dangerous when a test doesn't fail. Instead, tests can become cross-contaminated when beforeEach
from previous tests affects variables in current test.
.compileComponents()
is synchronous, unless there are components with styleUrls
and templateUrl
(like in the case above). In this case it becomes asynchronous, and async
helper should be used:
// asynchronous block
beforeEach(async(() => {
TestBed.configureTestingModule({ ... })
.compileComponents();
}));
// synchronous block
beforeEach(() => {
myComponent = ...
});
As a rule of thumb, blocks should be wrapped with async
of fakeAsync
helper if there's a chance that block can be asynchronous.
When component classes are tested with TestBed
, they follow a lifecycle and their hooks are called automatically. Calling ngOnInit()
manually is not needed (as another answer explains) and will result in calling the hook twice.
You don't have to call ngOnInit()
manually to run the component's init().
Modify your code to below code
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);
});
})
Look at the line fixture.detectChanges();
First time when change detection happens components ngOnInit()
will be called.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With