Écriture d’un test Jest pour le service Wire
Objectifs de formation
Une fois cette unité terminée, vous pourrez :
- Répertorier les trois principaux adaptateurs des services Wire
- Présenter les données fictives destinées au service Wire
- Comprendre les variables réactives et leurs effets
Test du service @Wire
Pour lire les données Salesforce, les composants Web Lightning utilisent un service Wire réactif qui repose sur Lightning Data Service. Les composants utilisent @wire
dans leur classe JavaScript pour lire les données de l’un des adaptateurs Wire dans les modules lightning/ui*Api
.
Le service Wire est en partie réactif, car il prend en charge les variables réactives. Les variables réactives commencent par un $
. Lorsqu’une variable réactive change, le service Wire provisionne de nouvelles données. Si les données existent dans le cache client, il est possible qu’aucune demande réseau ne soit réalisée.
Nous utilisons l’utilitaire de test @salesforce/sfdx-lwc-jest
pour tester la manière dont ces composants gèrent les données et les erreurs à partir du service Wire.
Les tests nécessitent que vous disposiez d’un contrôle total sur les entrées consommées par votre test. Aucun code extérieur ni aucune dépendance de données n’est autorisé. Nous importons l’API utilitaire de test de sfdx-lwc-jest
pour simuler les données afin que notre test ne dépende pas de facteurs imprévisibles, tels que l’invocation à distance ou la latence du serveur.
Il existe trois adaptateurs qui simulent des données de service Wire.
- Adaptateur Wire générique : l’adaptateur générique émet des données à la demande lorsque vous appelez l’API emit(). Il n’inclut aucune information supplémentaire sur les données elles-mêmes.
- Adaptateur Wire de Lightning Data Service (LDS) : l’adaptateur LDS imite le comportement de Lightning Data Service et inclut des informations sur les propriétés des données.
- Adaptateur Wire Apex : l’adaptateur Wire Apex imite les appels à une méthode Apex et inclut tout état d’erreur.
Examinons un décorateur @wire
classique. Importez un adaptateur Wire à l’aide de la syntaxe d’importation nommée. Décorez une propriété ou une fonction avec @wire
et spécifiez l’adaptateur Wire. Chaque adaptateur Wire définit un type de données.
Ce code importe le champ Account.Name et l’utilise dans l’objet de configuration d’un adaptateur Wire.
import { LightningElement, api, wire } from 'lwc'; import { getRecord } from 'lightning/uiRecordApi'; import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name'; export default class Record extends LightningElement { @api recordId; @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] }) wiredRecord; }
Examinons-les de plus près.
- La ligne 8 utilise le décorateur
@wire
pour accéder à la méthode importéegetRecord
et transmet la variable réactive$recordId
comme premier argument. Le deuxième argument référence l’élémentAccount.Name
importé depuis le schéma de la ligne 3. - La ligne 9 peut être une propriété ou une fonction privée qui reçoit le flux de données à partir du service Wire. S’il s’agit d’une propriété, les résultats sont renvoyés à la propriété « data » (données) ou « error » (erreur) de la propriété. S’il s’agit d’une fonction, les résultats sont renvoyés dans un objet avec une propriété « data » et une propriété « error ».
Examinons maintenant les différents adaptateurs.
Utilisation de l’adaptateur Wire générique
Tout d’abord, nous utilisons le service @wire
avec CurrentPageReference.
Le service lightning-navigation propose des adaptateurs Wire et des fonctions permettant de générer une URL ou d’accéder à une référence de page. Nous allons utiliser CurrentPageReference
pour obtenir une référence à la page actuelle dans Salesforce et créer un test pour celle-ci.
- Dans Visual Studio Code, cliquez avec le bouton droit de la souris sur le dossier
lwc
et sélectionnez SFDX : création d’un composant Web Lightning. - Saisissez
wireCPR
pour nommer le nouveau composant. - Appuyez sur Entrée.
- Appuyez sur Entrée pour accepter l’emplacement par défaut,
force-app/main/default/lwc
. - Dans le nouveau dossier
wireCPR/__tests__
, ouvrez le fichierwireCPR.test.js
. - Remplacez le nouveau fichier par :
import { createElement } from 'lwc'; import WireCPR from 'c/wireCPR'; import { CurrentPageReference } from 'lightning/navigation'; // Mock realistic data const mockCurrentPageReference = require('./data/CurrentPageReference.json'); describe('c-wire-c-p-r', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } }); it('renders the current page reference in <pre> tag', () => { const element = createElement('c-wire-c-p-r', { is: WireCPR }); document.body.appendChild(element); // Select element for validation const preElement = element.shadowRoot.querySelector('pre'); expect(preElement).not.toBeNull(); // Emit data from @wire CurrentPageReference.emit(mockCurrentPageReference); return Promise.resolve().then(() => { expect(preElement.textContent).toBe( JSON.stringify(mockCurrentPageReference, null, 2) ); }); }); });
- Enregistrez le fichier et exécutez les tests.
Examinons-les de plus près.
- La ligne 3 a une nouvelle importation :
CurrentPageReference
. - La ligne 6 récupère un fichier comportant des données
PageReference
simulées. Nous ne l’avons pas encore créé, il s’agit donc de la première raison pour laquelle le test échoue.Test suite failed to run Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
Nous allons y remédier. - À la ligne 26, nous remplissons les données fictives en utilisant
emit()
. - La ligne 28 démarre l’élément Promise qui s’attend à ce que les données fictives soient mises à jour dans
preElement
.
Créons le fichier de données test et mettons à jour le code pour que le test réussisse. Commencez par créer un répertoire sous le répertoire __tests__
pour stocker le fichier de données fictives.
- Cliquez avec le bouton droit de la souris sur le répertoire
__tests__
et sélectionnez Nouveau dossier. - Saisissez
data
pour nommer le nouveau répertoire. - Appuyez sur Entrée.
- Cliquez avec le bouton droit de la souris sur le répertoire
data
et sélectionnez Nouveau fichier. - Saisissez
CurrentPageReference.json
. - Appuyez sur Entrée.
- Saisissez le bloc de code JSON suivant dans le nouveau fichier :
{ "type": "standard__navItemPage", "attributes": { "apiName": "Wire" }, "state": {} }
- Enregistrez le fichier et exécutez les tests.
- Le test génère le message d’erreur suivant :
expect(received).not.toBeNull() Received: null
Excellent. Même un test qui échoue peut vous aider à progresser, car vous devez identifier rapidement les problèmes au fur et à mesure que vous créez le code.
Ensuite, nous ajoutons le code HTML et JavaScript.
- Ouvrez
wireCPR.html
. - Ajoutez le code suivant entre les balises
template
:<lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67"> <pre>{currentPageRef}</pre> </lightning-card>
- Enregistrez le fichier.
- Ouvrez
wireCPR.js
et remplacez le code par ce qui suit :import { LightningElement, wire } from 'lwc'; import { CurrentPageReference } from 'lightning/navigation'; export default class WireCPR extends LightningElement { @wire(CurrentPageReference) pageRef; get currentPageRef() { return this.pageRef ? JSON.stringify(this.pageRef, null, 2) : ''; } }
- Enregistrez le fichier et exécutez les tests.
- Les tests réussissent.
Voyons ce qui se passe. Lorsque l’adaptateur @wire
est utilisé, il recherche les informations renvoyées par un service. Nous devons simuler ces données au lieu d’appeler le service pour les obtenir réellement. Cela nous permet de tester uniquement les éléments dont nous disposons actuellement et non les éléments hors de notre portée. Cela nous permet également de bénéficier de tests rapides.
Utilisation de l’adaptateur Wire de Lightning Data Service
Ensuite, nous utilisons @wire
avec Lightning Data Service (LDS). LDS nous donne un accès rapide aux objets personnalisés et standard. Notre composant récupère des données dans Salesforce à l’aide de LDS et les affiche. Nous allons créer un test pour vérifier que les données s’affichent comme prévu à l’aide de l’adaptateur LDS.
- Créez un composant Web Lightning dans Visual Studio Code.
- Définissez le nom sur
wireLDS
. - Remplacez le code dans le fichier test
wireLDS.test.js
par :import { createElement } from 'lwc'; import WireLDS from 'c/wireLDS'; import { getRecord } from 'lightning/uiRecordApi'; // Mock realistic data const mockGetRecord = require('./data/getRecord.json'); describe('c-wire-l-d-s', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } }); describe('getRecord @wire data', () => { it('renders contact details', () => { const element = createElement('c-wire-l-d-s', { is: WireLDS }); document.body.appendChild(element); // Emit data from @wire getRecord.emit(mockGetRecord); return Promise.resolve().then(() => { // Select elements for validation const nameElement = element.shadowRoot.querySelector('p.accountName'); expect(nameElement.textContent).toBe( 'Account Name: ' + mockGetRecord.fields.Name.value ); const industryElement = element.shadowRoot.querySelector('p.accountIndustry'); expect(industryElement.textContent).toBe( 'Industry: ' + mockGetRecord.fields.Industry.value ); const phoneElement = element.shadowRoot.querySelector('p.accountPhone'); expect(phoneElement.textContent).toBe( 'Phone: ' + mockGetRecord.fields.Phone.value ); const ownerElement = element.shadowRoot.querySelector('p.accountOwner'); expect(ownerElement.textContent).toBe( 'Owner: ' + mockGetRecord.fields.Owner.displayValue ); }); }); }); });
- Enregistrez le fichier et exécutez les tests.
- Le test échoue en raison du fichier de données fictives manquant, que nous allons créer.
Avant de faire cela, examinons le code test pour observer ce qui se passe.
- La ligne 3 a une nouvelle importation :
getRecord
.getRecord
provient de l’API LDS. - La ligne 6 simule à nouveau des données du fichier
getRecord.json
dans le répertoiredata
. - La ligne 23 utilise la méthode « emit » sur
getRecord
avec l’argumentmockGetRecord
. - La ligne 25 démarre le retour de l’élément
Promise
et nous vérifions que différents éléments sont mis à jour avec les données fictives.
Ensuite, nous créons le fichier de données fictives et les autres fichiers pour obtenir la réussite du test. Nous exécutons les tests après la création de chaque fichier pour observer la progression des erreurs de test jusqu’à la réussite des tests.
- Créez le répertoire
data
dans le répertoire__tests__
. - Créez le fichier de données test en le nommant
getRecord.json
. - Ajoutez le code suivant :
{ "apiName" : "Account", "childRelationships" : { }, "eTag" : "35f2effe0a85913b45011ae4e7dae39f", "fields" : { "Industry" : { "displayValue" : "Banking", "value" : "Banking" }, "Name" : { "displayValue" : null, "value" : "Company ABC" }, "Owner" : { "displayValue" : "Test User", "value" : { "apiName" : "User", "childRelationships" : { }, "eTag" : "f1a72efecde2ece9844980f21b4a0c25", "fields" : { "Id" : { "displayValue" : null, "value" : "005o0000000KEEUAA4" }, "Name" : { "displayValue" : null, "value" : "Test User" } }, "id" : "005o0000000KEEUAA4", "lastModifiedById" : "005o0000000KEEUAA4", "lastModifiedDate" : "2019-08-22T23:45:53.000Z", "recordTypeInfo" : null, "systemModstamp" : "2019-08-23T06:00:11.000Z" } }, "OwnerId" : { "displayValue" : null, "value" : "005o0000000KEEUAA4" }, "Phone" : { "displayValue" : null, "value" : "867-5309" } }, "id" : "0011J00001A3VFoQAN", "lastModifiedById" : "005o0000000KEEUAA4", "lastModifiedDate" : "2020-02-28T05:46:17.000Z", "recordTypeInfo" : null, "systemModstamp" : "2020-02-28T05:46:17.000Z" }
- Enregistrez le fichier et exécutez les tests.
- Le test échoue.
- Ouvrez
wireLDS.html
et saisissez le code suivant entre les balises « template » :<lightning-card title="Wire Lightning Data Service" icon-name="custom:custom108"> <template if:true={account.data}> <p class="accountName">Account Name: {name}</p> <p class="accountIndustry">Industry: {industry}</p> <p class="accountPhone">Phone: {phone}</p> <p class="accountOwner">Owner: {owner}</p> </template> <template if:true={account.error}> <p>No account found.</p> </template> </lightning-card>
- Enregistrez le fichier et exécutez les tests.
- Le test échoue à nouveau, mais nous y sommes presque. Il vous suffit d’ajouter le contrôleur JavaScript pour obtenir les données.
- Ouvrez
wireLDS.js
et remplacez tout son code par :import { LightningElement, api, wire } from 'lwc'; import { getRecord, getFieldValue } from 'lightning/uiRecordApi'; import NAME_FIELD from '@salesforce/schema/Account.Name'; import OWNER_NAME_FIELD from '@salesforce/schema/Account.Owner.Name'; import PHONE_FIELD from '@salesforce/schema/Account.Phone'; import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry'; export default class WireLDS extends LightningElement { @api recordId; @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, INDUSTRY_FIELD], optionalFields: [PHONE_FIELD, OWNER_NAME_FIELD] }) account; get name() { return getFieldValue(this.account.data, NAME_FIELD); } get phone() { return getFieldValue(this.account.data, PHONE_FIELD); } get industry(){ return getFieldValue(this.account.data, INDUSTRY_FIELD); } get owner() { return getFieldValue(this.account.data, OWNER_NAME_FIELD); } }
- Enregistrez le fichier et exécutez les tests.
- Les tests réussissent.
Mais que se passe-t-il si une erreur se produit lors de l’obtention des données ? Vous pouvez effectuer un test pour le découvrir. Ajoutons un nouveau bloc « describe » dans notre fichier test wireLDS.test.js
.
- Ajoutez le code suivant juste après le bloc describe « getRecord @wire data », afin qu’il se trouve à l’intérieur du bloc describe « c-wire-l-d-s ». Vous pouvez imbriquer des blocs describe pour clarifier les tests.
describe('getRecord @wire error', () => { it('shows error message', () => { const element = createElement('c-wire-l-d-s', { is: WireLDS }); document.body.appendChild(element); // Emit error from @wire getRecord.error(); return Promise.resolve().then(() => { const errorElement = element.shadowRoot.querySelector('p'); expect(errorElement).not.toBeNull(); expect(errorElement.textContent).toBe('No account found.'); }); }); });
- Enregistrez le fichier et exécutez les tests.
- Les tests réussissent, car vous appliquez la méthode
error()
surgetRecordAdapter
. Les données fictives comportant une erreur, la conditionaccount.error
sera donc vraie.
Utilisation de l’adaptateur Wire Apex
Explorons maintenant Apex et voyons comment nous pouvons utiliser @wire
pour le tester.
La classe Apex que le LWC importe est considérée comme une connexion externe qui doit être simulée. Cela signifie que nous pouvons effectuer des tests sans avoir besoin de créer la classe Apex. Il nous suffit de simuler la réponse attendue par l’appel Apex. Dans le cas présent, il s’agit de l’affichage des comptes renvoyés par la classe Apex. Nous allons créer des tests dont la réponse attendue est l’affichage des comptes lorsqu’ils sont renvoyés, et la présentation d’un message si aucun compte n’est renvoyé.
Élaborons le LWC qui utilise l’appel Apex.
- Créez un composant Web Lightning dans Visual Studio Code.
- Définissez le nom sur
wireApex
. - Remplacez le code dans le fichier test
wireApex.test.js
par :import { createElement } from 'lwc'; import WireApex from 'c/wireApex'; import getAccountList from '@salesforce/apex/AccountController.getAccountList'; // Realistic data with a list of contacts const mockGetAccountList = require('./data/getAccountList.json'); // An empty list of records to verify the component does something reasonable // when there is no data to display const mockGetAccountListNoRecords = require('./data/getAccountListNoRecords.json'); // Mock getAccountList Apex wire adapter jest.mock( '@salesforce/apex/AccountController.getAccountList', () => { const { createApexTestWireAdapter } = require('@salesforce/sfdx-lwc-jest'); return { default: createApexTestWireAdapter(jest.fn()) }; }, { virtual: true } ); describe('c-wire-apex', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } // Prevent data saved on mocks from leaking between tests jest.clearAllMocks(); }); describe('getAccountList @wire data', () => { it('renders six records', () => { const element = createElement('c-wire-apex', { is: WireApex }); document.body.appendChild(element); // Emit data from @wire getAccountList.emit(mockGetAccountList); return Promise.resolve().then(() => { // Select elements for validation const accountElements = element.shadowRoot.querySelectorAll('p'); expect(accountElements.length).toBe(mockGetAccountList.length); expect(accountElements[0].textContent).toBe(mockGetAccountList[0].Name); }); }); it('renders no items when no records are returned', () => { const element = createElement('c-wire-apex', { is: WireApex }); document.body.appendChild(element); // Emit data from @wire getAccountList.emit(mockGetAccountListNoRecords); return Promise.resolve().then(() => { // Select elements for validation const accountElements = element.shadowRoot.querySelectorAll('p'); expect(accountElements.length).toBe( mockGetAccountListNoRecords.length ); }); }); }); describe('getAccountList @wire error', () => { it('shows error panel element', () => { const element = createElement('c-wire-apex', { is: WireApex }); document.body.appendChild(element); // Emit error from @wire getAccountList.error(); return Promise.resolve().then(() => { const errorElement = element.shadowRoot.querySelector('p'); expect(errorElement).not.toBeNull(); expect(errorElement.textContent).toBe('No accounts found.'); }); }); }); });
- Enregistrez le fichier et exécutez les tests.
- Vous obtenez une erreur due au fichier de données fictives manquant.
Vous connaissez la plupart de ce code. Un nouvel élément, jest.clearAllMocks()
, est présent dans le code de nettoyage pour réinitialiser les données fictives entre les tests. Cette étape est nécessaire, car nous disposons de deux fichiers fictifs que nous utilisons pour deux tests différents. Le premier test s’attend à ce que l’appel Apex renvoie six comptes. Le deuxième test consiste à affirmer ce qui se passerait si aucun compte n’était trouvé. Le dernier test sert à affirmer ce qui se passerait si l’appel Apex générait une erreur.
Ajoutons les fichiers de données fictives et le reste du code.
- Créez le répertoire
data
dans le répertoire__tests__
. - Créez deux fichiers dans le nouveau répertoire
data
, que vous nommezgetAccountList.json
etgetAccountListNoRecords.json
. - Saisissez le code ci-dessous dans
getAccountList.json
:[ { "Id": "001o0000005w4fT", "Name": "Edge Communications" }, { "Id": "001o0000005w4fa", "Name": "United Oil & Gas Corporation" }, { "Id": "001o0000005w4fY", "Name": "Express Logistics and Transport" }, { "Id": "001o0000005w4fV", "Name": "Pyramid Construction Inc." }, { "Id": "001o0000005w4fX", "Name": "Grand Hotels & Resorts Ltd" }, { "Id": "001o000000k2NMs", "Name": "ABC Genius Tech Consulting" } ]
- Le fichier
getAccountListNoRecords.json
contient un objet JSON vide :[]
- Saisissez maintenant le code suivant entre les balises
template
danswireApex.html
:<lightning-card title="Wire Apex" icon-name="custom:custom107"> <template if:true={accounts}> <template for:each={accounts} for:item="account"> <p key={account.Id}>{account.Name}</p> </template> </template> <template if:true={error}> <p>No accounts found.</p> </template> </lightning-card>
- Terminez en remplaçant le code dans
wireApex.js
par ce qui suit :import { LightningElement, wire } from 'lwc'; import getAccountList from '@salesforce/apex/AccountController.getAccountList'; export default class WireApex extends LightningElement { accounts; error; @wire(getAccountList) wiredAccounts({ error, data }) { if(data) { this.accounts = data; this.error = undefined; } else if(error) { this.error = error; this.accounts = undefined; } } }
Notez que nous obtenons uniquement la méthodegetAccountList
de la classe ApexAccountController
. N’oubliez pas que cette méthode doit être annotée avec@AuraEnabled(cacheable=true)
pour qu’elle fonctionne avec les LWC.@wire
l’utilise pour remplir une fonction qui renvoie une propriétéerror
oudata
. - Enregistrez tous les fichiers et exécutez les tests.
- Les tests réussissent.
Dans l’unité suivante, vous découvrirez la création d’autres composants fictifs et les méthodes de test des composants Web Lightning avec Jest.
Ressources
- Guides pour développeurs : Composants Web Lightning : Utilisation du service Wire pour obtenir des données
- Guides pour développeurs : Composants Web Lightning : adaptateurs et fonctions Wire lightning/ui*Api
- Guides pour développeurs : Composants Web Lightning : Écriture de tests Jest pour les composants Web Lightning utilisant le service Wire
- Guide du développeur de l’API interface utilisateur : Récupération d’un enregistrement
- Extensions Salesforce pour Visual Studio Code : Composants Web Lightning : Test
- GitHub : salesforce/wire-service-jest-util
- GitHub : trailheadapps/lwc-recipes
- GitHub : wire-service-jest-util/docs/Migration de la version 2.x vers 3.x