Scénarios de test des composants

Les sections suivantes, qui constituent la majeure partie de ce guide, explorent des scénarios de test de composants courants.

Liaison de composant

Le BannerComponent actuel présente un texte de titre statique dans le modèle HTML.

Après quelques modifications, le BannerComponent présente un titre dynamique en se liant à la propriété title du composant, comme ceci.

Aussi simple que cela soit, vous décidez d'ajouter un test pour confirmer que le composant affiche réellement le bon contenu là où vous le pensez.

Requête pour le ‹h1›

Vous allez écrire une séquence de tests qui inspectent la valeur de l'élément ‹h1› qui enveloppe la liaison d'interpolation de la propriété title.

Vous mettez beforeEach à jour pour rechercher cet élément avec un code HTML standard querySelector et l'affecter à la variable h1.


createComponent() ne lie pas les données

Pour votre premier test, vous voudriez voir que l'écran affiche la valeur par défaut title. Votre instinct est d'écrire un test qui inspecte immédiatement ‹h1› :

Ce test échoue avec le message :

La liaison se produit lorsque Angular effectue une détection de changement.

En production, la détection des modifications s'active automatiquement lorsque Angular crée un composant ou que l'utilisateur entre une frappe ou une activité asynchrone (par exemple, AJAX) terminée.

Le TestBed.createComponent ne déclenche pas la détection de changement. Un fait confirmé dans le test révisé :


detectChanges()

Vous devez dire à TestBed d'éffectuer la liaison de données en appelant fixture.detectChanges(). C'est seulement à ce moment-là que ‹h1› à le titre attendu.

La détection de changement retardé est intentionnelle et utile. Il donne au testeur l'occasion d'inspecter et de modifier l'état du composant avant qu'Angular initie la liaison de données et appelle les points d'ancrage du cycle de vie.

Voici un autre test qui modifie la propriété title du composant avant d'appeler fixture.detectChanges().


Détection automatique des modifications

Les BannerComponent tests appellent fréquemment detectChanges. Certains testeurs préfèrent que l'environnement de test Angular exécute la détection des modifications automatiquement.

C'est possible en configurant le TestBed avec le ComponentFixtureAutoDetect fournisseur. Commencez par l'importer de la bibliothèque de l'utilitaire de test :

Ajoutez-le ensuite au tableau providers de la configuration du module de test:
Voici trois tests qui illustrent le fonctionnement de la détection automatique des modifications.
Le premier test montre l'intérêt de la détection automatique des modifications.

Les deuxième et troisième tests révèlent une limitation importante. L'environnement de test Angular ne sait pas que le test a modifié le composant title. Le service ComponentFixtureAutoDetect répond aux activités asynchrones telles que la résolution des promesses, les minuteries et les événements DOM. Mais une mise à jour directe et synchrone de la propriété du composant est invisible. Le test doit appeler fixture.detectChanges() manuellement pour déclencher un autre cycle de détection de changement.

Plutôt que de se demander quand le dispositif de test effectuera ou non la détection des modifications, les exemples de ce guide appellent toujours detectChanges() explicitement. Il n'ya pas de mal à appeler detectChanges() plus souvent que ce qui est strictement nécessaire.

Modifier une valeur d'entrée avec le dispatchEvent()

Pour simuler une entrée utilisateur, vous pouvez rechercher l'élément d'entrée et définir sa propriété value.

Vous appelez fixture.detectChanges() pour déclencher la détection de changement d'Angular. Mais il y a une étape essentielle et intermédiaire.

Angular ne sait pas que vous avez défini la propriété value de l'élément en entrée. Il ne lira pas cette propriété tant que vous n'aurez pas déclenché l'événement de l'élément input en appelant dispatchEvent(). Alors tu appelles detectChanges().

L'exemple suivant montre la séquence appropriée.


Composant avec des fichiers externes

Ce qui précède BannerComponent est défini avec un modèle en ligne et un css en ligne, spécifiés dans les propriétés @Component.template et @Component.styles.

De nombreux composants spécifient des modèles externes et des css externes avec les propriétés @Component.templateUrl et @Component.styleUrls, comme la variante suivante de BannerComponent.

Cette syntaxe indique au compilateur Angular de lire les fichiers externes lors de la compilation du composant.

Ce n'est pas un problème lorsque vous exécutez la commande ng test du CLI car elle compile l'application avant d'exécuter les tests.

Toutefois, si vous exécutez les tests dans un environnement autre que l'interface de ligne de commande, les tests de ce composant peuvent échouer. Par exemple, si vous exécutez les tests BannerComponent dans un environnement de codage Web tel que Plunker, le message suivant apparaît :

Vous obtenez ce message d'échec de test lorsque l'environnement d'exécution compile le code source lors des tests eux-mêmes.

Pour corriger le problème, appelez compileComponents() comme expliqué ci-dessous.

Composant avec une dépendance

Les composants ont souvent des dépendances de service.

Le WelcomeComponent affiche un message de bienvenue à l'utilisateur connecté. Il sait qui est l'utilisateur depuis une propriété de l'injecté UserService :

La logique WelcomeComponent de décision qui interagit avec le service est une logique qui rend ce composant intéressant à tester. Voici la configuration du module de test pour le fichier de spécification welcome.component.spec.ts :

Cette fois, en plus de déclarer le composant à tester, la configuration ajoute un fournisseur UserService à la liste providers. Mais pas le réel UserService.

Fournir des tests de service en double

Un composant à tester ne doit pas nécessairement être injecté avec de vrais services. En fait, il est généralement préférable qu'il s'agisse de tests en double (stubs, fakes, spies ou mocks). Le but de la spécification est de tester le composant, pas le service, et les services réels peuvent poser problème.

Injecter le réel UserService pourrait être un cauchemar. Le service réel peut demander à l'utilisateur des informations d'identification de connexion et tenter d'atteindre un serveur d'authentification. Ces comportements peuvent être difficiles à intercepter. Il est beaucoup plus facile et sûr de créer et d'enregistrer un double test à la place du réel UserService.

Cette suite de tests particulière fournit une maquette minimale du UserService qui répond aux besoins du WelcomeComponent et de ses tests:


Obtenir le services injectés

Les tests doivent avoir accès au (stub) UserService injecté dans le WelcomeComponent.

Angular a un système d'injection hiérarchique. Il peut y avoir des injecteurs à plusieurs niveaux, de l'injecteur racine créé par le TestBed de bass de l'arborescence des composants.

Le moyen le plus sûr d'obtenir le service injecté, la méthode qui fonctionne toujours, consiste à l'obtenir à partir de l'injecteur du composant à tester. L'injecteur de composant est une propriété de l'appareil DebugElement.


TestBed.get()

Vous pouvez également obtenir le service auprès de l'injecteur de racine via TestBed.get(). C'est plus facile à retenir. Mais cela ne fonctionne que lorsque Angular injecte le composant avec l'instance de service dans l'injecteur racine du test.

Dans cette suite de tests, le seul fournisseur de UserService est le module de test racine; vous pouvez donc appeler TestBed.get() en toute sécurité comme suit :

Pour un cas d'utilisation dans lequel TestBed.get() ne fonctionne pas, voir la section "Remplacer les fournisseurs de composants" qui explique quand et pourquoi vous devez obtenir le service à partir de l'injecteur du composant.

Toujours obtenir le service à partir d'un injecteur

Ne faites pas référence à l'objet userServiceStub fourni au module de test dans le corps de votre test. ça ne marche pas !!! L'instance userService injectée dans le composant est un objet complètement différent, un clone de fourni userServiceStub.

Configuration finale et tests

Voici le code complet de beforeEach(), en utilisant TestBed.get():

Et voici quelques tests:

Le premier est un test de bon fonctionnement; cela confirme que le stubbed UserService est appelé et fonctionne.

Le deuxième paramètre de Jasmine Matcher (par exemple 'expected name') est une étiquette d'échec facultative. Si l'attente échoue, Jasmine affiche cette étiquette au message d'échec de l'attente. Dans une spécification avec plusieurs attentes, cela peut aider à clarifier ce qui a mal tourné et quelles attentes ont échoué.

Les tests restants confirment la logique du composant lorsque le service renvoie des valeurs différentes. Le deuxième test valide l'effet de la modification du nom d'utilisateur. Le troisième test vérifie que le composant affiche le message approprié lorsqu'il n'y a aucun utilisateur connecté.

Composant avec service async

Dans cet exemple, le modèle AboutComponent héberge a TwainComponent. Les TwainComponent affiches Mark Twain cite.

Notez que la valeur de la propriété quote du composant passe par un AsyncPipe. Cela signifie que la propriété retourne un Promise ou un Observable.

Dans cet exemple, la méthode TwainComponent.getQuote() vous indique que la propriété quote retourne un Observable.

Le TwainComponent obtient des citations d'un injecté TwainService. Le composant commence le retour Observable avec une valeur de marque de réservation ( '...') avant que le service puisse renvoyer son premier devis.

Le catchErrorservice intercepte les erreurs, prépare un message d'erreur et renvoie la valeur d'espace réservé sur le canal de réussite errorMessage. Pour éviter de mettre à jour ce message deux fois au cours du même cycle de détection de changement, vous devez attendre une coche pour le définir.

Ce sont toutes les fonctionnalités que vous voudrez tester.

Test avec un espion

Lors du test d'un composant, seule l'API publique du service doit être importante. En général, les tests eux-mêmes ne doivent pas appeler des serveurs distants. Ils devraient imiter de tels appels. La configuration dans cette app/twain/twain.component.spec.ts montre une façon de faire :

Concentrez-vous sur l'espion.

L'espion est conçu pour que tout appel à recevoir getQuote un observable avec un devis de test. Contrairement à la méthode getQuote() réelle, cet espion contourne le serveur et renvoie un observable synchrone dont la valeur est disponible immédiatement.

Vous pouvez écrire de nombreux tests utiles avec cet espion, même s'il Observable est synchrone.

tests synchrones

Un avantage clé d'un synchrone Observable est que vous pouvez souvent transformer des processus asynchrones en tests synchrones.

étant donné que le résultat de l'espionnage est renvoyé de manière synchrone, la méthode getQuote() met à jour le message à l'écran immédiatement après le premier cycle de détection de changement au cours duquel Angular appelle ngOnInit.

Vous n'êtes pas aussi chanceux lorsque vous testez le chemin d'erreur. Bien que l'espion de service renvoie une erreur de manière synchrone, la méthode du composant appelle setTimeout(). Le test doit attendre au moins un tour complet du moteur JavaScript avant que la valeur ne soit disponible. Le test doit devenir asynchrone.

Test async avec le fakeAsync()

Pour utiliser les fonctionnalités fakeAsync(), vous devez importer zone-testing.

Le test suivant confirme le comportement attendu lorsque le service renvoie un ErrorObservable.

Notez que la fonction it() reçoit un argument de la forme suivante.

La fonction fakeAsync() permet un style de codage linéaire en exécutant le corps de test dans un programme spécial fakeAsync test zone. Le corps du test semble être synchrone. Il n'y a pas de syntaxe imbriquée (comme a Promise.then()) pour perturber le flux de contrôle.

Le fonction tick()

Vous devez appeler tick() pour avancer l'horloge (virtuelle).

L'appel tick() simule le passage du temps jusqu'à la fin de toutes les activités asynchrones en attente. Dans ce cas, il attend le gestionnaire d'erreurs setTimeout();

La fonction tick() accepte les millisecondes en paramètre (la valeur par défaut est 0 si elle n'est pas fournie). Le paramètre représente l'avancée de l'horloge virtuelle. Par exemple, si vous avez un setTimeout(fn, 100) dans un test fakeAsync(), vous devez utiliser tick(100) pour déclencher le rappel fn.

Cette fonction tick() est l'un des utilitaires de test Angular que vous importez avec TestBed. C'est un compagnon fakeAsync() et vous ne pouvez l'appeler que dans un corps fakeAsync().

Comparaison des dates dans le fakeAsync()

fakeAsync() simule le passage du temps, ce qui vous permet de calculer la différence entre les dates à l'intérieur fakeAsync().


jasmine.clock avec le fakeAsync()

Jasmine fournit également une fonctionnalité clock permettant de se moquer des dates. Angular exécute automatiquement les tests qui sont exécutés après jasmine.clock().install() est appelée dans une méthode fakeAsync() jusqu'à ce qu'elle jasmine.clock().uninstall() soit appelée. fakeAsync() n'est pas nécessaire et génère une erreur si imbriqué.

Par défaut, cette fonctionnalité est désactivée. Pour l'activer, définissez un indicateur global avant l'importation zone-testing.

Si vous utilisez la CLI Angular, configurez cet indicateur dans src/test.ts.



Utilisation du planificateur RxJS dans le fakeAsync()

Vous pouvez également utiliser le planificateur RxJS de la fakeAsync() même manière que vous utilisez setTimeout() ou setInterval(), mais vous devez importer un correctif zone.js/dist/zone-patch-rxjs-fake-async pour le planificateur RxJS.


Soutenir plus macroTasks

Par défaut, fakeAsync() prend en charge les éléments suivants macroTasks.
  • setTimeout
  • setInterval
  • requestAnimationFrame
  • webkitRequestAnimationFrame
  • mozRequestAnimationFrame
Si vous exécutez d'autres macroTask tels que HTMLCanvasElement.toBlob(), l'erreur "Unknown macroTask scheduled in fake async" sera levée.
canvas.component.spec.ts

canvas.component.ts

Si vous souhaitez prendre en charge ce type de cas, vous devez définir le type macroTask de prise en charge que vous souhaitez prendre en charge beforeEach(). Par exemple :


L'observables async

Vous pourriez être satisfait de la couverture de test de ces tests.

Mais vous pourriez être troublé par le fait que le vrai service ne se comporte pas vraiment de cette façon. Le service réel envoie des demandes à un serveur distant. Un serveur met du temps à répondre et la réponse ne sera certainement pas disponible immédiatement comme lors des deux tests précédents.

Vos tests reflètent le monde réel plus fidèlement si vous retournez un asynchrone observable de l'espion getQuote() comme celui-ci.


Async aides observables

L'observable async a été produit par un assistant asyncData. L'aide asyncData est une fonction utilitaire que vous devrez écrire vous-même. Ou vous pouvez copier celui-ci à partir de l'exemple de code.

L'observable de cet assistant émet la valeur data lors du prochain tour du moteur JavaScript.

L'opérateur RxJSdefer() renvoie un observable. Il faut une fonction d'usine qui retourne une promesse ou une observable. Lorsqu'un élément s'abonne pour différer l'observable, il ajoute l'abonné à un nouvel observable créé avec cette fabrique.

L'opérateur defer() transforme le Promise.resolve() en une nouvelle observable qui, comme HttpClient, émet une fois et complète. Les abonnés sont désabonnés après avoir reçu la valeur de données.

Il existe une aide similaire pour générer une erreur asynchrone.


Tests asynchrones

Maintenant que l'espion getQuote() retourne des observables asynchrones, la plupart de vos tests devront également être asynchrones.

Voici un test fakeAsync() qui montre le flux de données que vous attendez dans le monde réel.

Notez que l'élément quote affiche la valeur de l'espace réservé ('...') après ngOnInit(). La première citation n'est pas encore arrivée.

Pour vider la première citation de l'observable, vous appelez tick(). Appelez ensuite detectChanges() pour dire à Angular de mettre à jour l'écran.

Ensuite, vous pouvez affirmer que l'élément quote affiche le texte attendu.

Test async avec async()

Pour utiliser les fonctionnalités async(), vous devez importer zone-testing.

La fonction fakeAsync() utilitaire a quelques limitations. En particulier, cela ne fonctionnera pas si le corps de test passe un appel XHR.

Les appels XHR dans un test sont rares, vous pouvez donc généralement vous en tenir à fakeAsync(). Mais si vous avez besoin d'appeler XHR, vous voudrez savoir async().

La méthode TestBed.compileComponents() appelle XHR à lire les fichiers de modèle et css externes lors de la compilation "juste à temps". Ecrire des tests qui appellent compileComponents() avec l'async() utilitaire.

Voici le test fakeAsync() précédent , ré-écrit avec l'utilitaire async().

L'utilitaire async() masque un passe-partout asynchrone en faisant en sorte que le code du testeur soit exécuté dans une zone de test asynchrone spéciale. Vous n'avez pas besoin de passer done() le test de Jasmine au test et à l'appel done() car il s'agit d'une promesse undefined ou de rappels observables.

Mais la nature asynchrone du test est révélée par l'appel à fixture.whenStable(), qui rompt le flux de contrôle linéaire.

Si vous utilisez un intervalTimer() tel que setInterval() dans async(), n'oubliez pas d'annuler le chronomètre clearInterval() après le test, sinon le programme async() ne se termine jamais.

whenStable

Le test doit attendre que l'observable getQuote() émette le prochain devis. Au lieu d'appeler tick(), il appelle fixture.whenStable().

Les déclarations fixture.whenStable() d'une promesse qui résout lorsque la file d'attente des tâches du moteur JavaScript est vide. Dans cet exemple, la file d'attente des tâches devient vide lorsque l'observable émet le premier devis.

Le test reprend dans le rappel de promesse, qui appelle detectChanges() à mettre à jour l'élément quote avec le texte attendu.

Jasmine done()

Bien que les fonctions async() et fakeAsync() simplifient grandement les tests asynchrones d'Angular, vous pouvez toujours revenir à la technique traditionnelle et transmettre une fonction qui prend un appel doner.

Vous ne pouvez pas appeler done() dans async() ou fakeAsync() fonctions, parce que le done parameter est undefined.

Vous êtes maintenant responsable du chaînage des promesses, du traitement des erreurs et des appels done() aux moments appropriés.

écrire des fonctions de test avec done(), est plus lourd que async() et fakeAsync(). Mais il est parfois nécessaire que le code implique le intervalTimer() même setInterval.

Voici deux autres versions du test précédent, écrites avec done(). Le premier s'abonne à l'exposé Observable au modèle par la propriété quote du composant.

L'opérateur last() RxJS émet la dernière valeur de l'observable avant la fin, qui sera le devis de test. Le subscribecallback appelle detectChanges() à mettre à jour l'élément quote avec le devis test, de la même manière que les tests précédents.

Dans certains tests, vous êtes plus intéressé par la façon dont une méthode de service injectée a été appelée et par ses valeurs renvoyées que par ce qui apparaît à l'écran.

Un espion de service, tel que l'espion qetQuote() du faux TwainService, peut vous donner cette information et faire des affirmations sur l'état de la vue.


Essais de marbre de composants

Les tests TwainComponent précédents simulaient une réponse observable asynchrone à partir des utilitaires TwainServicewith asyncData et asyncError.

Ce sont des fonctions courtes et simples que vous pouvez écrire vous-même. Malheureusement, ils sont trop simples pour de nombreux scénarios courants. Un observable émet souvent plusieurs fois, peut-être après un délai important. Un composant peut coordonner plusieurs éléments observables avec des séquences de valeurs et d'erreurs se chevauchant.

Le test sur marbre RxJS est un excellent moyen de tester des scénarios observables, simples et complexes. Vous avez probablement vu les diagrammes en marbre illustrant le fonctionnement des observables. Le test de marbre utilise un langage de marbre similaire pour spécifier les flux observables et les attentes dans vos tests.

Les exemples suivants reprennent deux des tests TwainComponent avec test au marbre.

Commencez par installer le paquet jasmine-marbles npm. Puis importez les symboles dont vous avez besoin.

Voici le test complet pour obtenir un devis:

Notez que le test de Jasmine est synchrone. Il n'y a pas fakeAsync(). Le test de marbre utilise un planificateur de test pour simuler le passage du temps dans un test synchrone.

La beauté des tests de marbre réside dans la définition visuelle des flux observables. Ce test définit une observable à froid qui attend trois images (---), émet une valeur (x) et complète (|). Dans le deuxième argument, vous mappez le marqueur de valeur (x) sur la valeur émise (testQuote).

La bibliothèque de billes construit l'observable correspondant, que le test définit comme valeur getQuote de retour de l'espion.

Lorsque vous êtes prêt à activer les observables de bille, vous dites TestScheduler à la vider sa file d'attente de tâches préparées comme celle-ci.

Cette étape sert un but analogue à tick() et whenStable() dans les précédents fakeAsync() et async() exemples. Le solde du test est le même que ces exemples.

Test d'erreur de marbre

Voici la version de test de marbre du test d'erreur getQuote().

C'est toujours un test asynchrone, appelant fakeAsync() et tick(), car le composant appelle lui-même setTimeout() lors du traitement des erreurs.

Regardez la définition de marbre observable.

C'est un observable à froid qui attend trois trames, puis émet une erreur. Le hash(#) indique le moment de l'erreur spécifié dans le troisième argument. Le second argument est nul car l'observable n'émet jamais de valeur.

En savoir plus sur le test des marbres

Un cadre en marbre est une unité virtuelle de temps de test. Chaque symbole ( -, x, |, #) marque le passage d'une trame.

Une observable froide ne produit pas de valeurs tant que vous n'y êtes pas abonné. La plupart de vos observables d'application sont froides. Toutes les méthodes HttpClient renvoient des observables à froid.

Un observable chaud produit déjà des valeurs avant de vous y abonner. L' observable Router.events, qui rapporte l'activité du routeur, est un observable à chaud.

Le test de marbre RxJS est un sujet riche qui dépasse le cadre de ce guide. Apprenez-le sur le Web, en commençant par la documentation officielle.

Composant avec entrées et sorties

Un composant avec des entrées et des sorties apparaît généralement dans le modèle de vue d'un composant hôte. L'hôte utilise une liaison de propriété pour définir la propriété d'entrée et une liaison d'événement pour écouter les événements générés par la propriété de sortie.

L'objectif de test est de vérifier que ces liaisons fonctionnent comme prévu. Les tests doivent définir les valeurs d'entrée et écouter les événements de sortie.

Le DashboardHeroComponen test un petit exemple d'un composant dans ce rôle. Il affiche un héros individuel fourni par le DashboardComponent. En cliquant sur ce héros, on indique DashboardComponent que l'utilisateur a sélectionné le héros.

Le DashboardHeroComponent est intégré dans le modèle DashboardComponent comme ceci:

dashboard.component.html (extrait)

Le DashboardHeroComponent apparaît dans un répéteur *ngFor, qui définit la propriété d'entrée de chaque composant hero sur la valeur de bouclage et écoute l'événement du composant selected.

Voici la définition complète du composant:
dashboard-hero.component.ts (composant)

Tout en testant un composant aussi simple, sa valeur intrinsèque est faible, il est utile de savoir comment. Vous pouvez utiliser l'une de ces approches :
  • Testez le comme utilisé par DashboardComponent.
  • Testez-le en tant que composant autonome.
  • Testez-le comme utilisé par un substitut pour DashboardComponent.
Un rapide coup d'oeil sur le DashboardComponentconstructeur décourage la première approche:
dashboard.component.ts (constructeur)

Le DashboardComponent dépend du routeur Angular et du HeroService. Vous devrez probablement les remplacer tous les deux par des doubles de test, ce qui représente beaucoup de travail. Le routeur semble particulièrement difficile.

L'objectif immédiat est de tester la DashboardHeroComponent, et non la DashboardComponent, alors essayez les deuxième et troisième options.

Tester le DashboardHeroComponent autonome

Voici la viande de la configuration du fichier de spécifications.
dashboard-hero.component.spec.ts (configuration)

Notez que le code d'installation assigne un test hero (expectedHero) à la propriété hero du composant, en émulant la manière dont il le définirait DashboardComponent via la liaison de la propriété dans son répéteur.

Le test suivant vérifie que le nom du héros est propagé au modèle via une liaison.

étant donné que le modèle passe le nom du héros via Angular UpperCasePipe, le test doit faire correspondre la valeur de l'élément avec le nom en majuscule.

Ce petit test montre comment des tests Angular peuvent vérifier la représentation visuelle d'un composant, chose impossible avec les tests de classe de composants, à faible coût et sans recourir à des tests de bout en bout beaucoup plus lents et compliqués.

En cliquant

Cliquer sur le héros devrait déclencher un événement selected que le composant hôte DashboardComponent peut (probablement) entendre :

La propriété selected du composant renvoie un EventEmitter, qui ressemble à un RxJS synchrone Observable pour les consommateurs. Le test y est explicitement abonné, tout comme le composant hôte le fait implicitement.

Si le composant se comporte comme prévu, cliquez sur l'élément du héros pour indiquer à la propriété selected du composant d'émettre l'objet hero.

Le test détecte cet événement via son abonnement à selected.

triggerEventHandler

Le test heroDe précédent est un DebugElement qui représente le héros ‹div›.

Il possède des propriétés Angular et des méthodes qui permettent une interaction abstraite avec l'élément natif. Ce test appelle le DebugElement.triggerEventHandler avec le nom de l'événement "click". La liaison d'événement "click" répond en appelant DashboardHeroComponent.click().

Angular DebugElement.triggerEventHandler peut soulever n'importe quel événement lié aux données par son nom. Le deuxième paramètre est l'objet événement transmis au gestionnaire.

Le test a déclenché un événement "clic" avec un objet null événement.

Le test suppose (correctement dans ce cas) que le gestionnaire d'événements au moment de l'exécution, la méthode click() du composant, ne se préoccupe pas de l'objet événement.

Les autres manipulateurs pardonnent moins. Par exemple, la RouterLink directive attend un objet avec une propriété button qui identifie quel bouton de la souris (le cas échéant) a été enfoncé pendant le clic. La directive RouterLink génère une erreur si l'objet événement est manquant.

Cliquez sur l'élément

La variante de test suivante appelle la méthode click() de l'élément natif, ce qui convient parfaitement à ce composant.


L'assistance click()

Cliquer sur un bouton, une ancre ou un élément HTML arbitraire est une tâche de test courante.

Rendre cela cohérent et facile en encapsulant le processus de déclenchement par clic dans un assistant tel que la fonction click() ci-dessous:
index.ts (assistant de clic)

Le premier paramètre est l'élément-à-cliquer. Si vous le souhaitez, vous pouvez passer un objet événement personnalisé en tant que second paramètre. La valeur par défaut est un objet d'événement de souris gauche (partiel) accepté par de nombreux gestionnaires, y compris la RouterLinkdirective.

La fonction click() d'assistance ne fait pas partie des utilitaires de test Angular. C'est une fonction définie dans l'exemple de code de ce guide. Tous les exemples de tests l'utilisent. Si vous l'aimez, ajoutez-le à votre propre collection d'aides.

Voici le test précédent, réécrit à l'aide de l'aide au clic.
dashboard-hero.component.spec.ts (test avec assistant de clic)


Composant à l'intérieur d'un hôte de test

Les tests précédents ont joué le rôle de l'hôte DashboardComponent eux-mêmes. Mais DashboardHeroComponent fonctionne-t-il correctement lorsque les données sont correctement liées à un composant hôte ?

Vous pouvez tester avec le réel DashboardComponent. Mais cela pourrait nécessiter beaucoup d'installation, en particulier lorsque son modèle comporte un répéteur *ngFor, d'autres composants, une présentation HTML, des liaisons supplémentaires, un constructeur qui injecte plusieurs services et qu'il commence immédiatement à interagir avec ces services.

Imaginez l'effort de désactiver ces distractions, juste pour prouver un point qui peut être fait de manière satisfaisante avec un hôte de test comme celui-ci:

dashboard-hero.component.spec.ts (hôte de test)

Cet hôte de test se lie comme DashboardHeroComponent le DashboardComponent voudrait mais sans le bruit du Router, du HeroServiceou du répéteur *ngFor.

L'hôte de test définit la propriété hero d'entrée du composant avec son héros de test. Il lie l'événement selected du composant à son gestionnaire onSelected, qui enregistre le héros émis dans sa propriété selectedHero.

Plus tard, les tests pourront facilement vérifier selectedHero que l'événement DashboardHeroComponent.selected a bien été émis par le héros attendu.

La configuration des tests de l'hôte de test est similaire à celle des tests autonomes :

dashboard-hero.component.spec.ts (configuration de l'hôte de test)

Cette configuration du module de test montre trois différences importantes:
  • Il déclare à la fois le DashboardHeroComponentet le TestHostComponent.
  • Il crée le TestHostComponentlieu du DashboardHeroComponent.
  • Le TestHostComponentdéfinit le DashboardHeroComponent.heroavec une liaison.
Le createComponentrenvoie un fixturequi contient une instance de TestHostComponentau lieu d'une instance de DashboardHeroComponent.

La création de TestHostComponent a pour effet secondaire de créer un DashboardHeroComponent car ce dernier apparaît dans le modèle du premier. La requête pour l'élément hero (heroEl) le trouve toujours dans le DOM de test, mais plus profondément que précédemment dans l'arborescence des éléments.

Les tests eux-mêmes sont presque identiques à la version autonome :

dashboard-hero.component.spec.ts (hôte de test)

Seul le test d'événement sélectionné diffère. Cela confirme que le héros DashboardHeroComponent sélectionné trouve vraiment son chemin à travers la liaison d'événement au composant hôte.

Composant de routage

Un composant de routage est un composant qui indique à l'utilisateur Router de naviguer vers un autre composant. Le DashboardComponent est un composant de routage car l'utilisateur peut y accéder HeroDetailComponent en cliquant sur l'un des boutons du héros du tableau de bord.

Le routage est assez compliqué. Tester l'apparence DashboardComponent semblait intimidant, en partie parce qu'il implique la Router, qu'il injecte en même temps que le HeroService.

dashboard.component.ts (constructeur)

Mocker HeroService avec un espion est une histoire familière. Mais le Router a une API compliquée et est lié à d'autres conditions préalables de services et d'applications. Peut-il être difficile de Mocker ?

Heureusement, pas dans ce cas parce que DashboardComponent cela ne fait pas grand chose avec le Router.

dashboard.component.ts (goToDetail)

C'est souvent le cas avec les composants de routage. En règle générale, vous testez le composant, pas le routeur, et veillez à ce que le composant navigue avec la bonne adresse dans les conditions données.

Fournir un espion de routeur pour cette suite de tests de composants est aussi simple que de fournir un espion HeroService.

dashboard.component.spec.ts (espions)

Le test suivant clique sur le héros affiché et confirme qu'il Router.navigateByUrl est appelé avec l'URL attendue.

dashboard.component.spec.ts (test de navigation)


Composants routés

Un composant routé est la destination d'une navigation Router. Il peut être plus difficile à tester, en particulier lorsque la route vers le composant inclut des paramètres. Le HeroDetailComponent est un composant routé qui est la destination d'un tel itinéraire.

Lorsqu'un utilisateur clique sur un héros du tableau de bord DashboardComponent, il indique à l'utilisateur de Router heroes/:id. Le :id est un paramètre de route dont la valeur est celle du id héros à éditer.

Les résultats Router de cette URL à un itinéraire vers HeroDetailComponent. Il crée un objet ActivatedRoute avec les informations de routage et l'injecte dans une nouvelle instance du fichier HeroDetailComponent.

Voici le constructeur HeroDetailComponent :

hero-detail.component.ts (constructeur)

Le composant HeroDetail a besoin du paramètre id pour pouvoir récupérer le héros correspondant via le fichier HeroDetailService. Le composant doit extraire l'id de la appropriété ActivatedRoute.paramMap qui est un Observable.

Il ne peut pas simplement faire référence à la propriété id du ActivatedRoute.paramMap. Le composant doit souscrire à l'observable ActivatedRoute.paramMap et être préparé au changement d'id au cours de sa vie.

hero-detail.component.ts (ngOnInit)

Les tests peuvent explorer la manière dont les réponses HeroDetailComponent aux différentes valeurs id de paramètre sont manipulées en manipulant l'injecté ActivatedRoute dans le constructeur du composant.

Vous savez comment espionner le Router et un service de données.

Vous allez adopter une approche différente avec ActivatedRoute parce que :
  • paramMap retourne un Observable qui peut émettre plus d'une valeur pendant un test.
  • Vous avez besoin de la fonction d'aide du routeur convertToParamMap(), pour créer un fichier ParamMap.
  • Les autres tests de composants routés nécessitent un double test pour ActivatedRoute.
Ces différences plaident en faveur d'une classe de bout réutilisable.

ActivatedRouteStub

La classe ActivatedRouteStub suivante sert de double test pour ActivatedRoute.

activé-route-stub.ts (ActivatedRouteStub)

Pensez à placer ces assistants dans un dossier testing frère du dossier app. Cet exemple met ActivatedRouteStub en testing/activated-route-stub.ts

Test avec le ActivatedRouteStub

Voici un test démontrant le comportement du composant lorsque l'observé id fait référence à un héros existant:

hero-detail.component.spec.ts (identifiant existant)

La méthode createComponent() et l'objet page sont discutés ci-dessous. Faites confiance à votre intuition pour le moment.

Lorsque le id ne peut pas être trouvé, le composant doit re-acheminer vers le HeroListComponent.

La configuration de la suite de tests fournissait le même espion de routeur décrit ci-dessus, qui espionnait le routeur sans naviguer réellement.

Ce test s'attend à ce que le composant tente de naviguer dans le fichier HeroListComponent.

hero-detail.component.spec.ts (identifiant incorrect)

Bien que cette application ne dispose pas d'une route vers le HeroDetailComponent qui omet le paramètre id, il peut ajouter une telle voie un jour. Le composant doit faire quelque chose de raisonnable quand il n'y en a pas id.

Dans cette implémentation, le composant doit créer et afficher un nouveau héros. Les nouveaux héros ont id=0 et un blanc name. Ce test confirme que le composant se comporte comme prévu :

hero-detail.component.spec.ts (pas d'identifiant)


Tests de composants imbriqués

Les modèles de composants ont souvent des composants imbriqués, dont les modèles peuvent contenir plus de composants.

L'arborescence des composants peut être très profonde et, la plupart du temps, les composants imbriqués ne jouent aucun rôle dans le test du composant situé en haut de l'arborescence.

Le AppComponent, par exemple, affiche une barre de navigation avec les ancres et leurs directives RouterLink.

app.component.html

Pendant que la AppComponent classe est vide, vous pouvez écrire des tests unitaires pour confirmer que les liens sont correctement câblés vers les directives RouterLink, peut-être pour les raisons expliquées ci-dessous.

Pour valider les liens, vous n'avez pas besoin de Router pour naviguer et vous n'avez pas besoin de marquer ‹router-outlet› l'endroit où insère les composants Router routés.

Le BannerComponent et WelcomeComponent (indiqué par ‹app-banner› et ‹app-welcome›) ne sont pas non plus pertinents.

Cependant, tout test qui crée le AppComponent DOM crée également des instances de ces trois composants et, si vous le permettez, vous devrez le configurer TestBed pour les créer.

Si vous négligez de les déclarer, le compilateur Angular ne reconnaîtra pas les balises ‹app-banner›, ‹app-welcome› et ‹router-outlet› du modèle AppComponent et une erreur.

Si vous déclarez les composants réels, vous devrez également déclarer leurs composants imbriqués et fournir tous les services injectés dans l'un des composants de l'arborescence.

C'est trop d'efforts pour répondre à quelques questions simples sur les liens.

Cette section décrit deux techniques permettant de minimiser la configuration. Utilisez-les, seuls ou en combinaison, pour rester concentré sur le test du composant principal.

Remplacement des composants inutiles

Dans la première technique, vous créez et déclarez des versions de stub des composants et de la directive qui jouent un rôle faible, voire nul, dans les tests.

app.component.spec.ts (déclaration de module de remplacement)

Les sélecteurs de tronçons correspondent aux sélecteurs des composants réels correspondants. Mais leurs modèles et leurs classes sont vides.

Ensuite, déclarez-les dans la configuration TestBed à côté des composants, des directives et des canaux devant être réels.

app.component.spec.ts (talons TestBed)

Le AppComponen test le sujet du test, donc bien sûr vous déclarez la version réelle.

Le RouterLinkDirectiveStub, décrit plus tard, est une version de test du réel RouterLink qui facilite les tests de lien.

Le reste sont des moignons.

NO_ERRORS_SCHEMA

Dans la deuxième approche, ajoutez NO_ERRORS_SCHEMA aux métadonnées TestBed.schemas.
app.component.spec.ts (NO_ERRORS_SCHEMA)

Le NO_ERRORS_SCHEMA dit au compilateur Angular d'ignorer les éléments et attributs non reconnus.

Le compilateur reconnaîtra l'élément ‹app-root› et l'attribut routerLink car vous avez déclaré un correspondant AppComponent et RouterLinkDirectiveStub dans la configuration TestBed.

Mais le compilateur génère pas d'erreur lorsqu'il rencontre ‹app-banner›, ‹app-welcome› ou ‹router-outlet›. Il les rend simplement sous forme de balises vides et le navigateur les ignore.

Vous n'avez plus besoin des composants de stub.

Utilisez les deux techniques ensemble

Ce sont des techniques pour le test de composants superficiels, ainsi nommées car elles réduisent la surface visuelle du composant à seulement les éléments du modèle du composant qui importent pour les tests.

L'approche NO_ERRORS_SCHEMA est la plus facile des deux mais n'en abusez pas.

NO_ERRORS_SCHEMA empêche également le compilateur de vous dire sur les composants manquants et les attributs que vous avez omis par inadvertance ou mal orthographié. Vous pourriez perdre des heures à chasser des bogues fantômes que le compilateur aurait détectés en un instant.

L'approche par composant de remplacement présente un autre avantage. Bien que les stubs de cet exemple soient vides, vous pouvez leur donner des modèles et des classes simplifiés si vos tests doivent interagir avec eux d'une manière ou d'une autre.

En pratique, vous combinerez les deux techniques dans la même configuration, comme dans cet exemple.
app.component.spec.ts (configuration mixte)

Le compilateur Angular crée le BannerComponentStub pour l'élément ‹app-banner› et l'applique RouterLinkStubDirective aux ancres avec l'attribut routerLink, mais ignore les balises ‹app-welcome› et ‹router-outlet›.

Composants avec RouterLink

Le réel RouterLinkDirective est assez compliqué et mêlé à d'autres composants et directives de la RouterModule. Cela nécessite une configuration difficile à simuler et à utiliser dans les tests.

Dans RouterLinkDirectiveStub, la directive réelle est remplacée par une version alternative conçue pour valider le type de câblage de balise d'ancrage présenté dans le modèle AppComponent.

app / app.component.spec.ts (configuration de test)

L'URL liée à l'attribut [routerLink] se connecte à la propriété linkParams de la directive.

HostListener connecte l'événement click de l'élément hôte (les ‹a› éléments d'ancrage dans AppComponent) à la méthode onClick de la directive stub.

Cliquer sur l'ancre devrait déclencher la méthode onClick (), qui définit la propriété indicative navigatedTo du stub. Les tests inspectent navigatedTo pour confirmer que cliquer sur l'ancre définit la définition de route attendue.

Remarque : La question de savoir si le routeur est configuré correctement pour naviguer avec cette définition d'itinéraire est une question pour un ensemble de tests séparé.

By.directive directives et injecté

Un peu plus de configuration déclenche la liaison de données initiale et obtient des références aux liens de navigation:

app / app.component.spec.ts (configuration de test)

Trois points d'intérêt particulier :

  • Vous pouvez localiser les éléments d'ancrage avec une directive attachée à l'aide de By.directive.
  • La requête retourne des DebugElementwrappers autour des éléments correspondants.
  • Chacune DebugElementexpose un injecteur de dépendance avec l'instance spécifique de la directive attachée à cet élément.

Les AppComponentliens à valider sont les suivants :

app / app.component.spec.ts (configuration de test)

Voici quelques tests qui confirment que ces liens sont connectés aux routerLinkdirectives comme prévu :

app / app.component.spec.ts (tests sélectionnés)

Remarque : Le test "click" dans cet exemple est trompeur. Il teste le RouterLinkDirectiveStubplutôt que le composant . C'est un défaut courant des stubs directives.

Il a un but légitime dans ce guide. Il montre comment rechercher un RouterLinkélément, cliquer dessus et inspecter un résultat, sans engager la totalité de la machine du routeur. Il vous faudra peut-être alors tester un composant plus sophistiqué, permettant de modifier l'affichage, de recalculer les paramètres ou de réorganiser les options de navigation lorsque l'utilisateur clique sur le lien.

A quoi servent ces tests ?

Les RouterLinktests de sélection peuvent confirmer qu'un composant avec des liens et une prise est configuré correctement, que le composant dispose des liens qu'il devrait avoir et qu'ils pointent tous dans la direction attendue. Ces tests ne concernent pas si l'application réussira à accéder au composant cible lorsque l'utilisateur clique sur un lien.

Stubbing RouterLink et RouterOutlet est la meilleure option pour ces objectifs de test limités. S'appuyer sur le vrai routeur les rendrait fragiles. Ils pourraient échouer pour des raisons indépendantes du composant. Par exemple, un protecteur de navigation pourrait empêcher un utilisateur non autorisé de consulter le site HeroListComponent. Ce n'est pas la faute du AppComponentet aucun changement à ce composant ne pourrait remédier à l'échec du test.

Une autre batterie de tests peut déterminer si l'application navigue comme prévu en présence de conditions qui influent sur les gardes, comme si l'utilisateur est authentifié et autorisé.

Remarque : Une future mise à jour du guide expliquera comment écrire de tels tests avec le RouterTestingModule.


Utiliser un objet de page

La HeroDetailComponent est une vue simple avec un titre, deux champs de héros et deux boutons.



Mais la complexité des modèles est abondante, même dans cette forme simple.

app / hero / hero-detail.component.html

Tests qui exercent le besoin de composant ...

  • attendre qu'un héros arrive avant que des éléments apparaissent dans le DOM.
  • une référence au texte du titre.
  • une référence à la zone de saisie du nom pour l'inspecter et la définir.
  • références aux deux boutons pour qu'ils puissent cliquer dessus.
  • espions pour certaines des méthodes composant et routeur.

Même une petite forme telle que celle-ci peut créer un désordre de configuration conditionnelle et de sélection d'éléments CSS torturés.

Apprivoisez la complexité avec une Pageclasse qui gère l'accès aux propriétés du composant et encapsule la logique qui les définit.

Voici une telle Pageclasse pour lehero-detail.component.spec.ts

app / hero / hero-detail.component.spec.ts (Page)

Maintenant, les crochets importants pour la manipulation et l'inspection des composants sont parfaitement organisés et accessibles à partir d'une instance de Page.

Une createComponentméthode crée un pageobjet et remplit les blancs une fois le heroarrivé.

app / hero / hero-detail.component.spec.ts (createComponent)

Les tests HeroDetailComponent dans une section précédente montrent comment createComponentet comment page garder les tests courts et sur le message. Il n'y a pas de distractions: pas d'attente pour les promesses à résoudre et pas de recherche dans le DOM pour comparer les valeurs des éléments.

Voici quelques HeroDetailComponenttests supplémentaires pour renforcer ce point.

app / hero / hero-detail.component.spec.ts (tests sélectionnés)


Appel de compileComponents()

Si vous exécutez des tests dans un environnement autre que l'interface de ligne de commande , les tests peuvent échouer avec le message suivant:


La racine du problème est qu'au moins un des composants impliqués dans le test spécifie un modèle externe ou un fichier CSS comme version suivante BannerComponent.

app / banner / banner-external.component.ts (modèle externe et css)

Le test échoue lorsque vous tentez TestBed de créer le composant.

app / banner / banner.component.spec.ts (une configuration qui échoue)

Rappelez-vous que l'application n'a pas été compilée. Ainsi, lorsque vous appelez createComponent(), la TestBedcompilation est implicite.

Ce n'est pas un problème lorsque le code source est en mémoire. Mais cela BannerComponentnécessite des fichiers externes que la compilation doit lire à partir du système de fichiers, opération intrinsèquement asynchrone .

Si TestBedon leur permettait de continuer, les tests seraient exécutés et échoueraient mystérieusement avant la fin du compilateur.

Le message d'erreur préemptif vous dit de compiler explicitement avec compileComponents().

compileComponents() est async

Vous devez appeler compileComponents()dans une fonction de test asynchrone.

Remarque :

Si vous omettez de rendre la fonction de test asynchrone (par exemple, oubliez de l'utiliser async()comme décrit ci-dessous), vous verrez ce message d'erreur

Une approche typique consiste à diviser la logique d'installation en deux beforeEach()fonctions distinctes :
  • Un asynchrone beforeEach()qui compile les composants
  • Un synchrone beforeEach()qui effectue la configuration restante.
Pour suivre ce modèle, importez l'assistant async() avec les autres symboles de test.

Le beforeEach async

Écrivez le premier asynchrone beforeEachcomme ceci.

app / banner / banner-external.component.spec.ts (async beforeEach)

La async()fonction d'assistance prend une fonction sans paramètre avec le corps de la configuration.

La TestBed.configureTestingModule()méthode retourne la TestBedclasse afin que vous puissiez chaîner les appels à d'autres TestBedméthodes statiques telles que compileComponents().

Dans cet exemple, le BannerComponentest le seul composant à compiler. D'autres exemples configurent le module de test avec plusieurs composants et peuvent importer des modules d'application contenant davantage de composants. N'importe lequel d'entre eux pourrait nécessiter des fichiers externes.

La TestBed.compileComponentsméthode compile de manière asynchrone tous les composants configurés dans le module de test.

Remarque : Ne pas reconfigurer l' TestBedappel après compileComponents().

L'appel compileComponents()ferme l' TestBedinstance actuelle pour poursuivre la configuration. Vous ne pouvez plus appeler de TestBedméthodes de configuration, configureTestingModule() ni aucune des override...méthodes. Le TestBedjette une erreur si vous essayez.

Faites compileComponents()la dernière étape avant d'appeler TestBed.createComponent().


Le beforeEach synchrone

La seconde, synchrone, beforeEach()contient les étapes de configuration restantes, notamment la création du composant et la recherche des éléments à inspecter.

app / banner / banner-external.component.spec.ts (synchrone beforeEach)

Vous pouvez compter sur le testeur pour attendre la fin du premier processus asynchrone beforeEachavant d'appeler le second.

configuration consolidée

Vous pouvez consolider les deux beforeEach()fonctions en une seule, asynchrone beforeEach().

La méthode compileComponents() renvoie une promesse afin que vous puissiez effectuer les tâches d'installation synchrone après la compilation en déplaçant le code synchrone dans un then(...)rappel.

app / banner / banner-external.component.spec.ts (un avant chaque)


compileComponents() est inoffensif

Il n'ya pas de mal à appeler compileComponents()quand ce n'est pas nécessaire.

Le fichier de test du composant généré par les appels CLI compileComponents() même s'il n'est jamais requis lors de l'exécution ng test.

Les tests de ce guide n'appellent que compileComponentslorsque cela est nécessaire.


Configuration avec les importations de modules

Les tests de composants précédents configuraient le module de test avec quelques-uns declarationscomme ceci:

app / dashboard / dashboard-hero.component.spec.ts (configurez TestBed)

C'est DashboardComponentsimple. Il n'a pas besoin d'aide. Mais les composants plus complexes dépendent souvent d'autres composants, directives, tuyaux et fournisseurs, qui doivent également être ajoutés au module de test.

Heureusement, le TestBed.configureTestingModuleparamètre correspond aux métadonnées transmises au décorateur, ce qui signifie que vous pouvez également spécifier et .@NgModuleprovidersimports

Le HeroDetailComponentnécessite beaucoup d'aide malgré sa petite taille et sa construction simple. Outre le support reçu du module de test par défaut CommonModule, il a besoin de:

  • NgModelet amis FormsModulepour activer la liaison de données bidirectionnelle.
  • Le TitleCasePipedu shareddossier.
  • Services de routeur (que ces tests sont en train de remplacer).
  • Services d'accès aux données Hero (également stubbed).

Une approche consiste à configurer le module de test à partir des pièces individuelles comme dans cet exemple:

app / hero / hero-detail.component.spec.ts (configuration de FormsModule)

Remarque : Notez que le beforeEach()est asynchrone et appelle TestBed.compileComponents car il HeroDetailComponenta un modèle externe et un fichier css. Comme expliqué dans Appel de compileComponents () ci-dessus, ces tests peuvent être exécutés dans un environnement non-CLI où Angular devrait les compiler dans le navigateur.

Importer un module partagé

Parce que de nombreux composants d'application ont besoin de FormsModuleet TitleCasePipe, le développeur a créé une SharedModulepour combiner ces éléments et d'autres éléments fréquemment demandés.

La configuration de test peut utiliser le SharedModuleaussi, comme indiqué dans cette configuration alternative:

app / hero / hero-detail.component.spec.ts (configuration SharedModule)

C'est un peu plus étroit et plus petit, avec moins d'instructions d'importation (non affichées).

Importer un module de fonctionnalité

Le HeroDetailComponentfait partie du HeroModule module Fonctions qui regroupe un plus grand nombre de pièces interdépendantes, y compris le SharedModule. Essayez une configuration de test qui importe le HeroModulemême que celui-ci:

app / hero / hero-detail.component.spec.ts (configuration de HeroModule)

C'est vraiment croustillant. Seul le test double dans le providersreste. Même la HeroDetailComponentdéclaration est parti.

En fait, si vous essayez de le déclarer, Angular lancera une erreur car il HeroDetailComponentest déclaré à la fois dans HeroModuleet DynamicTestModule créé par TestBed.

Remarque L'importation du module de fonctionnalité du composant peut être le moyen le plus simple de configurer des tests lorsqu'il existe de nombreuses dépendances mutuelles au sein du module et que le module est petit, contrairement aux modules de fonctionnalité.


Remplacer les fournisseurs de composants

Le HeroDetailComponentfournit son propre HeroDetailService.

app / hero / hero-detail.component.ts (prototype)

Il n'est pas possible d'empiler le composant HeroDetailServicedans providersle TestBed.configureTestingModule. Ce sont des fournisseurs pour le module de test , pas le composant. Ils préparent l'injecteur de dépendance au niveau du gabarit .

Angular crée le composant avec son propre injecteur, qui est un enfant de l'injecteur d'appareil. Il enregistre les fournisseurs du composant ( HeroDetailServicedans ce cas) avec l'injecteur pour enfant.

Un test ne peut pas accéder aux services d'injection d'enfant à partir de l'injecteur de fixation. Et TestBed.configureTestingModuleje ne peux pas les configurer non plus.

Angular crée de nouvelles instances du réel depuis le HeroDetailServicedébut!

Remarque :

Ces tests peuvent échouer ou expirer si l'utilisateur HeroDetailServiceeffectue ses propres appels XHR vers un serveur distant. Il peut ne pas y avoir de serveur distant à appeler.

Heureusement, la HeroDetailServiceresponsabilité des délégués pour l'accès aux données à distance d'un injecté HeroService.

app / hero / hero-detail.service.ts (prototype)

La configuration de test précédente remplace le réel HeroServicepar un TestHeroService qui intercepte les requêtes du serveur et simule leurs réponses.

Et si vous n'êtes pas aussi chanceux. Et si simuler HeroServiceest difficile? Et si HeroDetailServicefait ses propres demandes de serveur?

La TestBed.overrideComponentméthode peut remplacer les composants providerspar des doubles de test faciles à gérer , comme le montre la variante d'installation suivante:

app / hero / hero-detail.component.spec.ts (Configuration prioritaire)

Notez que TestBed.configureTestingModulene fournit plus un (faux) HeroServiceparce que ce n'est pas nécessaire .

Le méthode overrideComponent

Focus sur la overrideComponentméthode.

app / hero / hero-detail.component.spec.ts (overrideComponent)

Il faut deux arguments: le type de composant à override ( HeroDetailComponent) et un objet de métadonnées de remplacement. L' objet de métadonnées de substitution est un générique défini comme suit:


Un objet de substitution de métadonnées peut ajouter ou supprimer des éléments dans les propriétés de métadonnées ou réinitialiser complètement ces propriétés. Cet exemple réinitialise les providersmétadonnées du composant .

Le paramètre type T, est le type de métadonnées que vous transmettriez au décorateur:@Component


Fournir un espion espion ( HeroDetailServiceSpy )

Cet exemple remplace complètement le providerstableau du composant par un nouveau tableau contenant un HeroDetailServiceSpy.

Le HeroDetailServiceSpyest une version abrégée du réel HeroDetailService qui simule toutes les fonctionnalités nécessaires de ce service. Il n'injecte ni ne délègue de délégués au niveau inférieur, HeroService il n'est donc pas nécessaire de prévoir un test double pour cela.

Les HeroDetailComponenttests associés affirmeront que les méthodes de la HeroDetailService ont été appelées en espionnant les méthodes de service. En conséquence, le talon met en oeuvre ses méthodes en tant qu'espions:

app / hero / hero-detail.component.spec.ts (HeroDetailServiceSpy)

Le tests de dérogation

Désormais, les tests peuvent contrôler directement le héros du composant en manipulant celui-ci testHero et confirmer que les méthodes de service ont été appelées.

app / hero / hero-detail.component.spec.ts (tests de substitution)

Plus de substitution

La TestBed.overrideComponentméthode peut être appelée plusieurs fois pour le même composant ou pour des composants différents. Les TestBedoffres sont similaires overrideDirective, overrideModuleet les overridePipeméthodes pour creuser et remplacer des parties de ces autres classes.

Explorez les options et les combinaisons vous-même.