web-dev-qa-db-fra.com

test unitaire angular2: impossible de lire la propriété de componentInstance.method () undefined

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.

5
DaenKhaleesi

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.

4
Estus Flask

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.

4
Amit Chigadani

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.

0
Aleksi