Schreiben eines Jest-Tests für den Wire-Service
Lernziele
Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
- Nennen der drei Hauptadapter für Wire-Services
- Erläutern der Datensimulation für den Wire-Service
- Erklären reaktiver Variablen und ihrer Auswirkung
Testen des @Wire-Service
Lightning-Webkomponenten verwenden zum Lesen von Salesforce-Daten einen reaktiven Wire-Service, der auf Lightning Data Service aufsetzt. Komponenten verwenden @wire
in ihrer JavaScript-Klasse, um Daten aus einem der Wire-Adapter in den lightning/ui*Api
-Modulen zu lesen.
Der Wire-Service ist zum Teil reaktiv, da er reaktive Variablen unterstützt. Reaktive Variablen tragen das Präfix $
. Wenn sich eine reaktive Variable ändert, stellt der Wire-Service neue Daten bereit. Sind die Daten im Client-Cache enthalten, ist eventuell keine Netzwerkanforderung notwendig.
Wir verwenden das Test-Dienstprogramm @salesforce/sfdx-lwc-jest
, um zu testen, wie diese Komponenten vom Wire-Service empfangene Daten und Fehler verarbeiten.
Für die Tests ist es notwendig, dass Sie die volle Kontrolle über die Eingabe für Ihren Test haben. Es dürfen keine Abhängigkeiten von externem Code oder externen Daten bestehen. Wir importieren die Testdienstprogramm-API aus sfdx-lwc-jest
, um die Daten zu simulieren, damit unser Test nicht von unvorhersehbaren Faktoren wie Remote-Aufrufen oder Serverlatenz abhängt.
Es gibt drei Adapter für die Simulation von Wire-Service-Daten.
- Generischer Wire-Adapter: Der generische Adapter gibt Daten bei Bedarf aus, wenn Sie die emit()-API aufrufen. Dieser Adapter übermittelt keine zusätzlichen Informationen über die Daten als solche.
- LDS-Wire-Adapter (Lightning Data Service): Der LDS-Adapter ahmt das Verhalten von Lightning Data Service nach und sendet Informationen über die Eigenschaften der Daten.
- Apex-Wire-Adapter: Der Apex-Wire-Adapter ahmt Aufrufe an eine Apex-Methode nach und sendet einen eventuellen Fehlerstatus.
Sehen wir uns einen typischen @wire
-Decorator an. Importieren Sie einen Wire-Adapter unter Verwendung von benannter Importsyntax. Versehen Sie eine Eigenschaft oder Funktion mit dem Decorator @wire
und geben Sie den Wire-Adapter an. Jeder Wire-Adapter definiert einen Datentyp.
Dieser Code importiert das Feld "Account.Name" und verwendet es im Konfigurationsobjekt eines Wire-Adapters.
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; }
Wir sehen uns das gleich genauer an.
- In Zeile 8 wird der
@wire
-Decorator verwendet, um auf die importiertegetRecord
-Methode zuzugreifen und die reaktive Variable$recordId
als ihr erstes Argument zu übergeben. Das zweite Argument ist ein Verweis auf das importierte FeldAccount.Name
aus dem Schema in Zeile 3. - Bei Zeile 9 kann es sich entweder um eine private Eigenschaft oder eine Funktion handeln, die den Datenstrom aus dem Wire-Service empfängt. Handelt es sich um eine Eigenschaft, werden die Ergebnisse an die Eigenschaft "data" oder "error" der Eigenschaft zurückgegeben. Handelt es sich um eine Funktion, werden die Ergebnisse in einem Objekt mit einer Eigenschaft "data" und einer Eigenschaft "error" zurückgegeben.
Sehen wir uns nun die einzelnen Adapter an.
Verwenden des generischen Wire-Adapters
Zuerst verwenden wir den @wire
-Service mit "CurrentPageReference".
Der Service "lightning-navigation" bietet Wire-Adapter und -Funktionen für die Erstellung eines URLs oder die Navigation zu einem Seitenverweis. Wir verwenden CurrentPageReference
, um einen Verweis auf die aktuelle Seite in Salesforce abzurufen und einen Test dafür zu erstellen.
- Klicken Sie in Visual Studio Code mit der rechten Maustaste auf den Ordner
lwc
und wählen Sie SFDX: Create Lightning Web Component aus. - Geben Sie als Namen der neuen Komponente
wireCPR
ein. - Drücken Sie die Eingabetaste.
- Drücken Sie die Eingabetaste, um die Standardeinstellung
force-app/main/default/lwc
zu übernehmen. - Wechseln Sie zum neuen Ordner
wireCPR/__tests__
und öffnen Sie die DateiwireCPR.test.js
. - Überschreiben Sie die Datei mit folgendem Code:
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) ); }); }); });
- Speichern Sie die Datei und führen Sie die Tests aus.
Wir sehen uns das gleich genauer an.
- In Zeile 3 gibt es einen neuen Import:
CurrentPageReference
. - In Zeile 6 wird eine Datei mit simulierten
PageReference
-Daten abgerufen. Da wir diese Datei noch nicht erstellt haben, ist dies unser erster Grund für ein Scheitern des Tests.Test suite failed to run Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
Das werden wir als Nächstes korrigieren. - In Zeile 26 werden die simulierten Daten mithilfe von
emit()
eingetragen. - Zeile 28 enthält den Beginn des Promise, bei dem erwartet wird, dass die simulierten Daten in
preElement
aktualisiert werden.
Wir erstellen nun zunächst die Testdatendatei und aktualisieren den Code, damit der Test erfolgreich abgeschlossen wird. Dazu erstellen wir zuerst unter dem Verzeichnis __tests__
ein neues Verzeichnis für die Speicherung der Datei mit simulierten Daten.
- Klicken Sie mit der rechten Maustaste auf das Verzeichnis
__tests__
und wählen Sie New Folder aus. - Geben Sie als Namen des neuen Verzeichnisses
data
ein. - Drücken Sie die Eingabetaste.
- Klicken Sie mit der rechten Maustaste auf das Verzeichnis
data
und wählen Sie New File aus. - Geben Sie
CurrentPageReference.json
ein. - Drücken Sie die Eingabetaste.
- Geben Sie den folgenden JSON-Codeblock in die neue Datei ein:
{ "type": "standard__navItemPage", "attributes": { "apiName": "Wire" }, "state": {} }
- Speichern Sie die Datei und führen Sie die Tests aus.
- Der Test liefert die folgende Fehlermeldung.
expect(received).not.toBeNull() Received: null
Ausgezeichnet! Selbst ein fehlschlagender Test kann Fortschritte ermöglichen, da Sie dadurch beim Durcharbeiten des Codes schnell Probleme identifizieren können.
Als Nächstes fügen wir den HTML- und JavaScript-Code hinzu.
- Öffnen Sie
wireCPR.html
. - Fügen Sie innerhalb der
template
-Tags den folgenden Code hinzu:<lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67"> <pre>{currentPageRef}</pre> </lightning-card>
- Speichern Sie die Datei.
- Öffnen Sie die Datei
wireCPR.js
und ersetzen Sie den Code durch Folgendes: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) : ''; } }
- Speichern Sie die Datei und führen Sie die Tests aus.
- Die Tests werden erfolgreich ausgeführt.
Sehen wir uns die Vorgänge im Einzelnen an. Wenn der @wire
-Adapter verwendet wird, sucht er nach Informationen, die von einem Service zurückgegeben wurden. Wir müssen diese Daten simulieren, anstatt tatsächlich einen Aufruf an den Service abzusetzen, um die Daten zu erhalten. Dadurch testen wir wirklich nur die Elemente, über die wir aktuell verfügen, und keine Objekte außerhalb des Testumfangs. Außerdem sorgt dies dafür, dass die Tests schnell ausgeführt werden.
Verwenden des Wire-Adapters von Lightning Data Service
Als Nächstes verwenden wir @wire
mit Lightning Data Service (LDS). LDS gibt uns schnellen Zugriff auf benutzerdefinierte und Standardobjekte. Unsere Komponente ruft mithilfe von LDS Daten aus Salesforce ab und zeigt sie an. Wir erstellen den Test, um zu überprüfen, ob die Daten wie erwartet mithilfe des LDS-Adapters angezeigt werden.
- Erstellen Sie eine neue Lightning-Webkomponente in Visual Studio Code.
- Legen Sie
wireLDS
als Namen fest. - Überschreiben Sie den Code in der Testdatei
wireLDS.test.js
: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 ); }); }); }); });
- Speichern Sie die Datei und führen Sie die Tests aus.
- Der Test schlägt fehl, da die Datei mit simulierten Daten fehlt, die wir als Nächstes erstellen.
Doch zuerst sehen wir uns den Testcode und die einzelnen Abläufe an.
- In Zeile 3 gibt es einen neuen Import:
getRecord
.getRecord
stammt aus der LDS-API. - In Zeile 6 werden die Daten wieder anhand der Datei
getRecord.json
im Verzeichnisdata
simuliert. - In Zeile 23 wird die emit-Methode auf
getRecord
angewendet, und zwar mitmockGetRecord
als Argument. - In Zeile 25 beginnt die Rückgabe des
Promise
. Dabei prüfen wir, dass verschiedene Elemente mit den simulierten Daten aktualisiert werden.
Als Nächstes erstellen wir die Datei mit simulierten Daten und auch die restlichen Dateien, damit der Test erfolgreich durchgeführt wird. Wir führen die Tests nach der Erstellung jeder Datei aus, um die Fortschritte bei den Testfehlern zu verfolgen, bis die Tests erfolgreich abgeschlossen werden.
- Erstellen Sie das Verzeichnis
data
im Verzeichnis__tests__
. - Erstellen Sie die Testdatendatei namens
getRecord.json
. - Fügen Sie den folgenden Code hinzu:
{ "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" }
- Speichern Sie die Datei und führen Sie die Tests aus.
- Der Test schlägt fehl.
- Öffnen Sie
wireLDS.html
und geben Sie den folgenden Code zwischen den template-Tags ein:<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>
- Speichern Sie die Datei und führen Sie die Tests aus.
- Der Test schlägt erneut fehl, doch wir sind schon fast am Ziel. Sie müssen das JavaScript-Steuerfeld hinzufügen, um die Daten abzurufen.
- Öffnen Sie die Datei
wireLDS.js
und überschreiben Sie den gesamten darin enthaltenen Code mit diesem Code: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); } }
- Speichern Sie die Datei und führen Sie die Tests aus.
- Die Tests werden erfolgreich ausgeführt.
Doch, was tun, wenn beim Abrufen der Daten ein Fehler auftritt? Sie können auch dafür einen Test erstellen. Fügen wir einen neuen describe-Block zu unserer Testdatei wireLDS.test.js
hinzu.
- Fügen Sie den folgenden Code direkt nach dem Block "describe 'getRecord @wire data'" hinzu, sodass er sich innerhalb des Blocks "describe 'c-wire-l-d-s'" befindet. Sie können describe-Blöcke verschachteln, um Tests klarer zu strukturieren.
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.'); }); }); });
- Speichern Sie die Datei und führen Sie die Tests aus.
- Die Tests werden erfolgreich ausgeführt, da Sie die
error()
-Methode aufgetRecordAdapter
anwenden. Dadurch tritt bei den simulierten Daten ein Fehler auf undaccount.error
erhält den Wert "true".
Verwenden des Apex-Wire-Adapters
Wenden wir uns nun Apex und der Verwendung von @wire
für Tests zu.
Die Apex-Klasse, die die Lightning-Webkomponente importiert, gilt als externe Verbindung, die simuliert werden muss. Das heißt, wir können testen, ohne die Apex-Klasse erstellen zu müssen. Wir müssen nur die erwartete Antwort des Apex-Aufrufs simulieren. In diesem Fall erwarten wir die Anzeige von Accounts, die von der Apex-Klasse zurückgegeben werden. Wir erstellen Tests, die erwarten, dass die Accounts angezeigt werden, wenn sie zurückgegeben werden, und eine Meldung erwarten, wenn keine zurückgegeben werden.
Lassen Sie uns jetzt die Lightning-Webkomponente erstellen, die die Apex-Klasse verwendet.
- Erstellen Sie eine neue Lightning-Webkomponente in Visual Studio Code.
- Legen Sie
wireApex
als Namen fest. - Überschreiben Sie den Code in der Testdatei
wireApex.test.js
: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.'); }); }); }); });
- Speichern Sie die Datei und führen Sie die Tests aus.
- Sie erhalten eine Fehlermeldung, da die Datei mit simulierten Daten fehlt.
Der Großteil dieses Codes ist uns bereits bekannt. Es gibt ein neues Element namens jest.clearAllMocks()
im Bereinigungscode, das die simulierten Daten zwischen Tests zurücksetzt. Dies ist notwendig, da wir zwei Simulationsdateien für zwei unterschiedliche Tests verwenden. Der erste Test prüft, ob der Apex-Aufruf sechs Accounts zurückgibt. Der zwei Test prüft, was passiert, wenn keine Accounts gefunden werden. Der letzte Test prüft, was passiert, wenn die Apex einen Fehler enthält.
Fügen wir nun die Dateien mit den simulierten Daten und den restlichen Code hinzu.
- Erstellen Sie das Verzeichnis
data
im Verzeichnis__tests__
. - Erstellen Sie im neuen Verzeichnis
data
zwei Dateien namensgetAccountList.json
undgetAccountListNoRecords.json
. - Geben Sie den nachfolgenden Code in
getAccountList.json
ein:[ { "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" } ]
- In die Datei
getAccountListNoRecords.json
wird ein leeres JSON-Objekt eingefügt:[]
- Fügen Sie nun den folgenden Code zwischen den
template
-Tags in die DateiwireApex.html
ein:<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>
- Ersetzen Sie abschließend den Code in
wireApex.js
durch diesen: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; } } }
Beachten Sie, dass wir nur diegetAccountList
-Methode aus der Apex-KlasseAccountController
abrufen. Denken Sie daran, dass die Methode mit der Anmerkung@AuraEnabled(cacheable=true)
versehen sein muss, um mit Lightning-Webkomponenten zu funktionieren.@wire
verwendet die Methode, um eine Funktion mit dem zurückgegebenenerror
- oderdata
-Wert zu füllen. - Speichern Sie alle Dateien und führen Sie die Tests aus.
- Die Tests werden erfolgreich ausgeführt.
In der nächsten Einheit befassen wir uns damit, wie wir andere Komponenten simulieren, und behandeln die restlichen Möglichkeiten, Lightning-Webkomponenten mit Jest zu testen.
Ressourcen
- Entwicklerhandbuch: Lightning-Webkomponenten: Verwenden des Wire-Service zum Abrufen von Daten (Englisch)
- Entwicklerhandbuch: Lightning-Webkomponenten: lightning/ui*Api – Wire-Adapter und -Funktionen (Englisch)
- Entwicklerhandbuch: Lightning-Webkomponenten: Schreiben von Jest-Tests für Lightning-Webkomponenten, die den Wire-Service verwenden (Englisch)
- Benutzeroberflächen-API – Entwicklerhandbuch: Abrufen eines Datensatzes (Englisch)
- Salesforce Extensions für Visual Studio Code: Lightning-Webkomponenten: Testen (Englisch)
- GitHub: salesforce/wire-service-jest-util
- GitHub: trailheadapps/lwc-recipes
- GitHub: wire-service-jest-util/docs/Migrating from version 2.x to 3.x