classe mycomponent.spec.ts:
Cela génère une erreur: Impossible de lire la propriété "ngOnInit" de non défini.
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);
});
});
ci-dessous est 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
);
}
}
ci-dessous est un service simulé
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);
}
}
Je suis novice dans les tests Angular2 et je veux que myService.getData renvoie DATA_OBJECT lorsque myComponent.ngOnInit () appelle la méthode myService.getData () dans ma classe spec. Aidez-moi à y parvenir.
Le problème est que beforeEach
asynchrone n'est pas implémenté correctement, cela entraîne des conditions de concurrence critique.
Faire .compileComponents().then(() => { ... })
dans beforeEach
bloc entraîne le retard de l'exécution du code dans then
rappel au moins pour un tick. it
le bloc n'attend jamais et accède à la variable myComponent
avant d'avoir une chance d'être assignée.
Ce type de conditions de course peut devenir moins évident et plus dangereux lorsqu'un test échoue. Au lieu de cela, les tests peuvent devenir contaminés de façon croisée lorsque beforeEach
des tests précédents affecte les variables du test en cours.
.compileComponents()
est synchrone, sauf s'il existe des composants avec styleUrls
et templateUrl
(comme dans le cas ci-dessus). Dans ce cas, il devient asynchrone et l'aide de async
doit être utilisée:
// asynchronous block
beforeEach(async(() => {
TestBed.configureTestingModule({ ... })
.compileComponents();
}));
// synchronous block
beforeEach(() => {
myComponent = ...
});
En règle générale, les blocs doivent être enveloppés avec async
de fakeAsync
helper s'il y a une chance que le bloc puisse être asynchrone.
Lorsque les classes de composants sont testées avec TestBed
, elles suivent un cycle de vie et leurs hooks sont appelés automatiquement. L'appel manuel de ngOnInit()
n'est pas nécessaire (comme l'explique une autre réponse) et entraînera l'appel du crochet deux fois.
Vous n'avez pas besoin d'appeler ngOnInit()
manuellement pour exécuter init () du composant.
Modifiez votre code en code ci-dessous
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);
});
})
Regardez la ligne fixture.detectChanges();
La première fois que la détection de changement se produit, les composants ngOnInit()
seront appelés.
Juste au cas où quelqu'un ne trouverait pas la réponse acceptée utile, cette erreur peut également se produire lorsque NgZone
n'est pas correctement moqué. Je ne comprends pas la mécanique sous-jacente, mais a fournissait auparavant NgZone
comme un littéral d'objet comme suit:
TestBed.configureTestingModule({
providers: [{
provide: NgZone,
useValue: {
runOutsideAngular: (fn: (...args: Array<any>) => T) => { return fn(); }
... other NgZone functions ...
}
}],
declarations: [MyComponent]
})
Cela a fonctionné dans certains tests pour une raison quelconque, donc je ne le soupçonnais pas au début, mais après un certain temps, j'ai créé une classe NgZone
simulée qui étend la NgZone
réelle:
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>();
}
Ensuite, juste la classe dans la configuration TestBed
:
TestBed.configureTestingModule({
providers: [{
provide: NgZone,
useClass: NgZoneMock
}],
declarations: [MyComponent]
})
Il convient de mentionner qu'il existe d'autres façons de le faire (et de se moquer de tout service en général). Voici quelques exemples Exécution de tests de jasmin pour un composant avec la dépendance NgZone . La création d'un objet espion Jasmine est assez utile mais je préfère personnellement que les maquettes soient dans des fichiers séparés à côté du fichier de service réel pour DRY. Bien sûr, vous pouvez également placer l'objet espion dans le fichier fictif.