É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-le de plus près. 

  • La ligne 8 utilise le décorateur @wire pour accéder à la méthode importée getRecord et transmet la variable réactive $recordId comme premier argument. Le deuxième argument référence l’élément Account.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. 

  1. 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.
  2. Saisissez wireCPR pour nommer le nouveau composant.
  3. Appuyez sur Entrée.
  4. Appuyez sur Entrée pour accepter l’emplacement par défaut, force-app/main/default/lwc.
  5. Cliquez avec le bouton droit de la souris sur le répertoire wireCPR que vous venez de créer et sélectionnez Nouveau dossier.
  6. Saisissez __tests__.
  7. Appuyez sur Entrée.
  8. Cliquez avec le bouton droit de la souris sur le répertoire __tests__ et sélectionnez Nouveau fichier.
  9. Saisissez wireCPR.test.js.
  10. Appuyez sur Entrée.
  11. Saisissez le bloc de code suivant dans le nouveau fichier :
    import { createElement } from 'lwc';
    import WireCPR from 'c/wireCPR';
    import { CurrentPageReference } from 'lightning/navigation';
    import { registerTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
      
    // Mock realistic data
    const mockCurrentPageReference = require('./data/CurrentPageReference.json');
      
    // Register a standard test wire adapter.
    const currentPageReferenceAdapter = registerTestWireAdapter(
      CurrentPageReference
    );
      
    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
        currentPageReferenceAdapter.emit(mockCurrentPageReference);
      
        return Promise.resolve().then(() => {
          expect(preElement.textContent).toBe(
            JSON.stringify(mockCurrentPageReference, null, 2)
          );
        });
      });
    });
  12. Enregistrez le fichier et exécutez les tests.

Examinons-le de plus près.

  • Les lignes 3 et 4 comportent deux nouvelles importations : CurrentPageReference et registerTestWireAdapter.
  • La ligne 7 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 10, nous utilisons registerTestWireAdapter avec CurrentPageReference que nous avons importé. Cela nous permettra de le simuler plus tard dans le test.
  • À la ligne 32, nous remplissons les données fictives en utilisant emit().
  • La ligne 34 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.

  1. Cliquez avec le bouton droit de la souris sur le répertoire __tests__ et sélectionnez Nouveau dossier.
  2. Saisissez data pour nommer le nouveau répertoire.
  3. Appuyez sur Entrée.
  4. Cliquez avec le bouton droit de la souris sur le répertoire data et sélectionnez Nouveau fichier.
  5. Saisissez CurrentPageReference.json.
  6. Appuyez sur Entrée.
  7. Saisissez le bloc de code JSON suivant dans le nouveau fichier :
    {
      "type": "standard__navItemPage",
      "attributes": {
        "apiName": "Wire"
      },
      "state": {}
    }
  8. Enregistrez le fichier et exécutez les tests.
  9. 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.

  1. Ouvrez wireCPR.html.
  2. Ajoutez le code suivant entre les balises template :
      <lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67">
        <pre>{currentPageRef}</pre>
      </lightning-card>
  3. Enregistrez le fichier.
  4. 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) : '';
      }
    }
  5. Enregistrez le fichier et exécutez les tests.
  6. 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.

  1. Créez un composant Web Lightning dans Visual Studio Code.
  2. Définissez le nom sur wireLDS.
  3. Créez le répertoire __tests__.
  4. Ajoutez un fichier test nommé wireLDS.test.js.
  5. Ajoutez le code suivant au fichier test :
    import { createElement } from 'lwc';
    import WireLDS from 'c/wireLDS';
    import { getRecord } from 'lightning/uiRecordApi';
    import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
      
    // Mock realistic data
    const mockGetRecord = require('./data/getRecord.json');
      
    // Register as an LDS wire adapter
    const getRecordAdapter = registerLdsTestWireAdapter(getRecord);
      
    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
          getRecordAdapter.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
            );
          });
        });
      });
    });
  6. Enregistrez le fichier et exécutez les tests.
  7. 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.

  • Les lignes 3 et 4 comportent de nouvelles importations : getRecord et registerLdsTestWireAdapter.
    getRecord provient de l’API LDS. registerLdsTestWireAdapter est l’adaptateur Jest spécifique permettant d’utiliser LDS.
  • La ligne 7 simule à nouveau des données du fichier getRecord.json dans le répertoire data.
  • La ligne 10 enregistre l’adaptateur Wire LDS avec getRecord, nous sommes donc prêts à utiliser les données fictives. C’est ce que nous faisons à la ligne 27.
  • La ligne 27 utilise la méthode « emit » sur getRecordAdapter avec l’argument mockGetRecord.
  • La ligne 29 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.

  1. Créez le répertoire data dans le répertoire __tests__.
  2. Créez le fichier de données test en le nommant getRecord.json.
  3. 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"
    }
  4. Enregistrez le fichier et exécutez les tests.
  5. Le test échoue.
  6. 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>
  7. Enregistrez le fichier et exécutez les tests.
  8. Le test échoue à nouveau, mais nous y sommes presque. Il vous suffit d’ajouter le contrôleur JavaScript pour obtenir les données.
  9. 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);
      }
    }
  10. Enregistrez le fichier et exécutez les tests.
  11. Les tests réussissent.
Remarque

Remarque

Les composants Web Lightning accèdent aux données et métadonnées Salesforce à partir de tous les objets personnalisés et standard pris en charge par l’API de l’interface utilisateur. Les objets externes ne sont pas pris en charge. 

Obtenez des données test en récupérant un instantané des données à l’aide d’un client REST pour accéder à l’API de l’interface utilisateur. Cette approche est plus précise que l’écriture manuelle du JSON. Voici un exemple de l’appel REST qui permet d’obtenir les données présentées ci-dessus (vous aurez besoin de votre propre ID de compte) :

/services/data/v47.0/ui-api/records/0011J00001A3VFo?fields=Account.Name,Account.Industry&optionalFields=Account.Phone,Account.Owner.Name

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

  1. 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
          getRecordAdapter.error();
      
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No account found.');
          });
        });
      });
  2. Enregistrez le fichier et exécutez les tests.
  3. Les tests réussissent, car vous appliquez la méthode error() sur getRecordAdapter. Les données fictives comportant une erreur, la condition account.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 avec comme réponse attendue 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.

  1. Créez un composant Web Lightning dans Visual Studio Code.
  2. Définissez le nom sur wireApex.
  3. Créez le répertoire __tests__.
  4. Ajoutez un fichier test nommé wireApex.test.js.
  5. Ajoutez le code ci-dessous au fichier test :
    import { createElement } from 'lwc';
    import WireApex from 'c/wireApex';
    import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
    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');
      
    // Register as Apex wire adapter. Some tests verify that provisioned values trigger desired behavior.
    const getAccountListAdapter = registerApexTestWireAdapter(getAccountList);
      
    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
          getAccountListAdapter.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
          getAccountListAdapter.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
          getAccountListAdapter.error();
      
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No accounts found.');
          });
        });
      });
    });
  6. Enregistrez le fichier et exécutez les tests.
  7. Vous obtenez une erreur due au fichier de données fictives manquant.

Vous connaissez la plupart de ce code. Notez que nous utilisons l’attribut registerApexTestWireAdapter pour travailler avec Apex, et non registerLdsTestWireAdapter. 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.

  1. Créez le répertoire data dans le répertoire __tests__.
  2. Créez deux fichiers dans le nouveau répertoire data, que vous nommez getAccountList.json et getAccountListNoRecords.json.
  3. 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"
      }
    ]
  4. Le fichier getAccountListNoRecords.json contient un objet JSON vide :
    []
  5. Saisissez maintenant le code suivant entre les balises template dans wireApex.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>
  6. 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éthode getAccountList de la classe Apex AccountController. 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 ou data.
  7. Enregistrez tous les fichiers et exécutez les tests.
  8. 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