84 votes

Mocking des composants enfants - Angular 2

Comment simuler un composant enfant lors d'un test ? J'ai un composant parent appelé product-selected dont le modèle ressemble à ceci :

<section id="selected-container" class="container-fluid">
    <hr/>
  <product-settings></product-settings>
  <product-editor></product-editor>
  <product-options></product-options>
</section>

La déclaration du composant ressemble à ceci :

import { Component, Input }               from '@angular/core'; 

import { ProductSettingsComponent } from '../settings/product-settings.component';                                      
import { ProductEditorComponent }   from '../editor/product-editor.component';                                      
import { ProductOptionsComponent }  from '../options/product-options.component';                                        

@Component({
    selector: 'product-selected',
    templateUrl: './product-selected.component.html',
    styleUrls: ['./product-selected.component.scss']
})
export class ProductSelectedComponent {}

Ce composant n'est en fait qu'un emplacement pour les autres composants et ne contiendra probablement pas d'autres fonctions.

Mais lorsque je mets en place les tests, j'obtiens l'erreur de modèle suivante, répétée pour les trois composants :

Error: Template parse errors:
    'product-editor' is not a known element:
    1. If 'product-editor' is an Angular component, then verify that it is part of this module.
    2. If 'product-editor' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
        <hr/>
      <product-settings></product-settings>
      [ERROR ->]<product-editor></product-editor>

J'ai essayé de charger une version simulée des composants enfants mais je ne sais pas comment faire - les exemples que j'ai vus ne font que surcharger le parent et ne mentionnent même pas les composants enfants. Alors, comment faire ?

114voto

Arnaud P Points 613

Attention aux NO_ERRORS_SCHEMA . Citons une autre partie de la même documentation :

Les tests de composants peu profonds avec NO_ERRORS_SCHEMA simplifient grandement les tests unitaires de modèles complexes. Cependant, le compilateur ne vous signale plus les erreurs telles que les composants et directives mal orthographiés ou mal utilisés.

Je trouve cet inconvénient tout à fait contraire aux objectifs de la rédaction d'un test. D'autant plus qu'il n'est pas très difficile de se moquer d'un composant de base.

Une approche qui n'a pas encore été mentionnée ici consiste simplement à les déclarer au moment de la configuration :

@Component({
  selector: 'product-settings',
  template: '<p>Mock Product Settings Component</p>'
})
class MockProductSettingsComponent {}

@Component({
  selector: 'product-editor',
  template: '<p>Mock Product Editor Component</p>'
})
class MockProductEditorComponent {}

...  // third one

beforeEach(() => {
  TestBed.configureTestingModule({
      declarations: [
        ProductSelectedComponent,
        MockProductSettingsComponent,
        MockProductEditorComponent,
        // ... third one
      ],
      providers: [/* your providers */]
  });
  // ... carry on
});

3 votes

J'utilise quelque chose comme ça pour tester les composants enfants avec Input et Outputs, et c'est très bien pour vérifier que les bindings fonctionnent dans les deux sens, cependant, c'est sujet au bit-rot, car un renommage d'une propriété Input dans le "vrai" composant enfant n'entraînera pas l'échec du test. Je me demande s'il est possible de générer un type Mock dynamique basé sur le composant enfant réel en inspectant simplement ses annotations d'une manière ou d'une autre. Comme chaque composant enfant peut avoir un ensemble inconnu d'injections de dépendances, il faudrait que ce soit quelque chose basé uniquement sur le type, et non sur une version instanciée du composant enfant.

1 votes

En résumé, je pense que ce qu'il faut faire dans ce cas, c'est tester également le composant enfant. La documentation contient un partie consacrée aux tests des entrées et des sorties où vous pourrez, je l'espère, trouver ce dont vous avez besoin. Si je comprends bien, vous aimerez probablement la partie qui suit directement également.

0 votes

Eh bien, l'exemple va dans l'autre sens. Comment tester la liaison d'un composant avec son parent. Cela ne sert à rien si vous avez un composant parent avec 4 composants enfants, et que vous voulez tester le composant parent de manière isolée, car vous devrez soit résoudre et fournir manuellement toutes les dépendances des composants enfants, soit maintenir manuellement une version Mock de chaque composant enfant. Idéalement, je pourrais diviser un composant en deux, une directive de base qui a l'entrée et la sortie, et une version héritée du composant qui fournit le modèle, mais cela ne fonctionnerait pas car ils devraient avoir le même sélecteur.

65voto

Soraz Points 1750

J'ai trouvé une solution presque parfaite, qui permet également de générer correctement des erreurs si quelqu'un remanie un composant : https://www.npmjs.com/package/ng-mocks

npm install ng-mocks --save-dev

Maintenant, dans votre fichier .spec.ts, vous pouvez faire

import { MockComponent } from 'ng-mocks';
import { ChildComponent } from './child.component.ts';
// ...
beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [FormsModule, ReactiveFormsModule, RouterTestingModule],
    declarations: [
      ComponentUnderTest,
      MockComponent(ChildComponent), // <- here is the thing
      // ...
    ],
  });
});

Cela crée un nouveau composant anonyme qui a le même sélecteur, @Input() y @Output() les propriétés de la ChildComponent mais sans code.

Supposons que votre ChildComponent a une @Input() childValue: number qui est lié à votre composant testé, <app-child-component [childValue]="inputValue" />

Bien qu'il ait été tourné en dérision, vous pouvez utiliser By.directive(ChildComponent) dans vos tests, ainsi que By.css('app-child-component') ainsi

it('sets the right value on the child component', ()=> {
  component.inputValue=5;
  fixture.detectChanges();
  const element = fixture.debugElement.query(By.directive(ChildComponent));
  expect(element).toBeTruthy();

  const child: ChildComponent = element.componentInstance;
  expect(child.childValue).toBe(5);
});

0 votes

Exemple mis à jour pour correspondre à Angular 6.1

0 votes

Afin d'installer ng-socks en mode dev : npm install ng-mocks --save-dev

0 votes

Tu m'as sauvé la vie

26voto

Milad Points 12206

Généralement, si vous avez un composant utilisé dans la vue du composant que vous testez et que vous ne voulez pas nécessairement déclarer ces composants parce qu'ils peuvent avoir leurs propres dépendances, afin d'éviter l'utilisation de l'option "quelque chose n'est pas un élément connu" vous devez utiliser NO_ERRORS_SCHEMA .

import { NO_ERRORS_SCHEMA }          from '@angular/core';

TestBed.configureTestingModule({
        declarations: declarations,
        providers: providers
        schemas:      [ NO_ERRORS_SCHEMA ]
  })

D'après les documents :

Ajoutez NO_ERRORS_SCHEMA aux métadonnées des schémas du module de test pour indiquer au compilateur d'ignorer les éléments et attributs non reconnus. Vous n'avez plus à déclarer des composants et des directives non pertinents.

Plus d'informations sur ce sujet : https://angular.io/docs/ts/latest/guide/testing.html#!#shallow-component-test

1 votes

Vous savez, j'étais sur la page des tests pour tester les Observables et je n'avais jamais remarqué cette partie. Merci - c'était une bonne lecture, même si j'aurais aimé la trouver plus tôt ! J'accepte votre réponse car elle est beaucoup plus simple que la mienne.

12 votes

Il s'agit d'une très mauvaise solution, car elle masque d'autres problèmes potentiels et va donc à l'encontre de l'objectif des tests de code. Arnaud P a donné une bonne réponse.

5 votes

La rédaction de composants fictifs peut prendre plus de temps, mais le jeu en vaut la chandelle. L'utilisation de NO_ERRORS_SCHEMA vous hantera plus tard.

12voto

Katana24 Points 1079

J'ai posé cette question afin de pouvoir poster une réponse car je me suis débattu avec ce problème pendant un jour ou deux. Voici comment procéder :

let declarations = [
  ProductSelectedComponent,
  ProductSettingsComponent,
  ProductEditorComponent,
  ProductOptionsComponent
];

beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: declarations,
            providers: providers
        })
        .overrideComponent(ProductSettingsComponent, {
            set: {
                selector: 'product-settings',
                template: `<h6>Product Settings</h6>`
            }
        })
        .overrideComponent(ProductEditorComponent, {
            set: {
                selector: 'product-editor',
                template: `<h6>Product Editor</h6>`
            }
        })
        .overrideComponent(ProductOptionsComponent, {
            set: {
                selector: 'product-options',
                template: `<h6>Product Options</h6>`
            }
        });

        fixture = TestBed.createComponent(ProductSelectedComponent);
        cmp = fixture.componentInstance;
        de = fixture.debugElement.query(By.css('section'));
        el = de.nativeElement;
    });

Vous devez enchaîner les overrideComponent pour chacun des composants enfants.

0 votes

Je pense que la première erreur n'est pas corrigée par la surcharge, il vous suffit de déclarer ces composants, ce que vous avez fait de toute façon, donc la surcharge est inutile à moins que vous n'en ayez réellement besoin.

0 votes

@Milad c'est vrai mais dans mon cas, que je n'ai pas inclus, c'est que ces composants ont leurs propres dépendances sur les services qui devraient alors être inclus pour eux. Parce que je ne me soucie que de tester ce composant et que je ne me soucie pas des autres au-delà d'un niveau de création très basique, je peux heureusement les surcharger.

0 votes

Est-ce que cela remplace vraiment l'ensemble du composant ou seulement les métadonnées. J'ai utilisé overrideComponent, mais l'un de mes enfants se plaignait toujours de l'absence d'un service (utilisé uniquement dans cet enfant).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X