Redactar una prueba de Jest para servicio de red
Objetivos de aprendizaje
Después de completar esta unidad, podrá:
- Enumerar los tres adaptadores principales para servicios de red.
- Explicar la simulación de datos para el servicio de red.
- Comprender variables reactivas y su efecto.
Prueba del servicio @Wire
Los componentes web Lightning utilizan un servicio de red reactivo creado en Lightning Data Service para leer datos de Salesforce. Los componentes utilizan @wire
en su clase de JavaScript para leer datos desde uno de los adaptadores de red en los módulos lightning/ui*Api
.
El servicio de red es reactivo en parte porque admite variables reactivas. Las variables tiene el prefijo $
. Cuando una variable reactiva cambia, el servicio de red proporciona nuevos datos. Si los datos existen en un caché de cliente, una solicitud de red podría no involucrarse.
Utilice la utilidad de prueba @salesforce/sfdx-lwc-jest
para probar cómo estos componentes gestionan datos y errores desde el servicio de red.
La prueba requiere que tenga control completo sobre el ingreso que su prueba consume. No se permite código externo o dependencias de datos. Importamos la API de utilidad de prueba desde sfdx-lwc-jest
para simular los datos de modo que nuestra prueba no dependa de factores imprescindibles como invocación remota o latencia de servidor.
Existen tres adaptadores para la simulación de datos de servicio de red.
- Adaptador de red genérico: El adaptador genérico emite datos on demand cuando llama la API emit(). No incluye ninguna información adicional acerca de los datos en si.
- Adaptador de red de Lightning Data Service (LDS): El adaptador de LDS imita el comportamiento de Lightning Data Service e incluye información acerca de las propiedades de los datos.
- Adaptador de red de Apex: El adaptador de red de Apex imita llamadas a un método de Apex e incluye cualquier estado de error.
Echemos un vistazo a un típico decorador @wire
. Importe un adaptador de red utilizando una sintaxis de importación denominada. Decore una propiedad o función con @wire
y especifique el adaptador de red. Cada adaptador de red define un tipo de datos.
Este código importa el campo Account.Name y lo utiliza en el objeto de configuración de un adaptador de red.
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; }
Echemos un vistazo más de cerca.
- Línea 8 está utilizando el decorator
@wire
para acceder al métodogetRecord
importado y aprobando en la variable$recordId
reactiva como su primer argumento. El segundo argumento es una referencia alAccount.Name
importado desde el esquema en Línea 3. - Línea 9 puede ser una propiedad privada o una función que recibe la transmisión de datos desde el servicio de red. Si es una propiedad, los resultados se devuelven a la propiedad de error o la propiedad de datos de la propiedad. Si es una función, los resultados se devuelven en un objeto con una propiedad de datos y una de error.
Ahora echemos un vistazo a los diferentes adaptadores.
Uso del adaptador de red genérico
En primer lugar, utilizamos el servicio @wire
con CurrentPageReference.
El servicio lightning-navigation ofrece adaptadores de res y funciones para generar una URL o navegar a una referencia de página. Utilizaremos CurrentPageReference
para obtener una referencia a la página actual en Salesforce y crear una prueba para ella.
- En Visual Studio Code, haga clic con el botón derecho en la carpeta
lwc
y seleccione SFDX: Crear componente web Lightning. - Ingrese
wireCPR
como nombre para el nuevo componente. - Pulse Entrar.
- Pulse Entrar para aceptar la ubicación
force-app/main/default/lwc
. - En la nueva carpeta
wireCPR/__tests__
, abra el archivowireCPR.test.js
. - Sobrescriba el nuevo archivo con lo siguiente:
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) ); }); }); });
- Guarde el archivo y ejecute las pruebas.
Echemos un vistazo más de cerca.
- Línea 3 tiene una nueva importación:
CurrentPageReference
. - Línea 6 toma un archivo con datos
PageReference
simulados. No creamos esto aún de modo que es nuestro primer motivo para que la prueba produzca un error.Test suite failed to run Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
Arreglaremos esto a continuación. - Línea 26 es donde rellenamos los datos simulados utilizando
emit()
. - Línea 28 inicia el Promise que espera que se actualicen los datos simulados en el
preElement
.
Creemos el archivo de datos de prueba y actualicemos el código para conseguir la aprobación de la prueba. Primero, creemos un nuevo directorio bajo el directorio __tests__
para almacenar el archivo de datos simulados.
- Haga clic con el botón derecho en el directorio
__tests__
y seleccione Nueva carpeta. - Ingrese
data
para el nombre del nuevo directorio. - Pulse Entrar.
- Haga clic con el botón derecho en el directorio
data
y seleccione Nuevo archivo. - Ingrese
CurrentPageReference.json
. - Pulse Entrar.
- Ingrese el siguiente bloque de código de json en el nuevo archivo:
{ "type": "standard__navItemPage", "attributes": { "apiName": "Wire" }, "state": {} }
- Guarde el archivo y ejecute las pruebas.
- La prueba obtiene este mensaje de error.
expect(received).not.toBeNull() Received: null
¡Excelente! Incluso una prueba fallida puede fomentar el progreso identificando rápidamente cualquier problema cuando trabaja en el código.
A continuación agregamos el código HTML y JavaScript.
- Abra
wireCPR.html
. - Agregue el siguiente código en las etiquetas
template
:<lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67"> <pre>{currentPageRef}</pre> </lightning-card>
- Guarde el archivo.
- Abra
wireCPR.js
y sustituya el código con lo siguiente: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) : ''; } }
- Guarde el archivo y ejecute las pruebas.
- Las pruebas se aprueban.
Veamos lo que sucede. Cuando se utiliza el adaptador @wire
, busca información devuelta desde un servicio. Debemos crear una simulación de esos datos para utilizar en lugar de realizar actualmente la llamada al servicio para obtener los datos. Estos hace que solo probemos los elementos que tenemos actualmente y no cosas fuera de nuestro ámbito. Esto también ayuda a mantener las pruebas rápidas.
Uso del adaptador de red de Lightning Data Service
A continuación, utilizamos @wire
con Lightning Data Service (LDS). LDS nos proporciona acceso rápido a objetos estándar y personalizados. Nuestros componentes obtienen los datos desde Salesforce empleando LDS y los muestran. Crearemos la prueba para verificar que los datos se muestran como se espera mediante el adaptador de LDS.
- Cree un nuevo componente web Lightning en Visual Studio Code.
- Establezca el nombre como
wireLDS
. - Sobrescriba el código en el archivo de prueba
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 ); }); }); }); });
- Guarde el archivo y ejecute las pruebas.
- La prueba falla debido a la falta del archivo de datos simulados que creamos a continuación.
Antes de hacer eso, echemos un vistazo al código de prueba para ver lo que está sucediendo.
- Línea 3 tiene una nueva importación:
getRecord
.getRecord
procede de la API de LDS. - Línea 6 está simulando los datos de nuevo desde el archivo
getRecord.json
en el directoriodata
. - Línea 23 utiliza el método emit en
getRecord
conmockGetRecord
como un argumento. - Línea 25 inicia la devolución de
Promise
y comprobamos que varios elementos están actualizados con los datos simulados.
A continuación, creamos el archivo de datos simulados y el resto de los archivos para obtener una prueba aprobada. Ejecutamos las pruebas reas la creación de cada archivo para ver el progreso de los errores de prueba hasta que se aprueban.
- Cree el directorio
data
en el directorio__tests__
. - Cree el archivo de datos de prueba con el nombre
getRecord.json
. - Agregue el código siguiente:
{ "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" }
- Guarde el archivo y ejecute las pruebas.
- La prueba falla.
- Abra
wireLDS.html
e ingrese el siguiente código entre las etiquetas de plantilla:<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>
- Guarde el archivo y ejecute las pruebas.
- La prueba falla de nuevo, pero ya casi terminamos. Solo necesita agregar el controlador de JavaScript para obtener los datos.
- Abra
wireLDS.js
y sobrescriba todo su código con: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); } }
- Guarde el archivo y ejecute las pruebas.
- Las pruebas se aprueban.
¿Pero qué sucede si existe un error en la obtención de los datos? Puede probar eso también. Agreguemos un nuevo bloque en nuestro archivo de prueba wireLDS.test.js
.
- Agregue el siguiente código justo después del bloque describe 'getRecord @wire data' de modo que esté dentro del bloque describe 'c-wire-l-d-s'. Puede anidar bloques describe para ayudar a aclarar pruebas.
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.'); }); }); });
- Guarde el archivo y ejecute las pruebas.
- Las pruebas se aprueban porque está utilizando el método
error()
en elgetRecordAdapter
. Esto causa un error en los datos simulados de modo queaccount.error
será verdadero.
Uso del adaptador de red de Apex
A continuación, indaguemos en Apex y veamos cómo podemos utilizar @wire
para probarlo.
La clase de Apex que LWC está importando se considera una conexión externa que se deberá simular. Esto significa que podemos probar sin necesidad de crear la clase de Apex. Todo lo que debemos hacer es simular la respuesta esperada de la llamada de Apex. En este caso esperamos mostrar Cuentas que se devuelven desde la clase de Apex. Crearemos pruebas que esperan que se muestren Cuentas cuando se devuelven, y se espera un mensaje si no se devuelve ninguna.
Creemos el LWC que la utiliza.
- Cree un nuevo componente web Lightning en Visual Studio Code.
- Establezca el nombre como
wireApex
. - Sobrescriba el código en el archivo de prueba
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.'); }); }); }); });
- Guarde el archivo y ejecute las pruebas.
- Obtiene un error para el archivo de datos simulados que falta.
La mayor parte del código es familiar. Existe un nuevo elemento, jest.clearAllMocks()
, en el código de limpieza para restablecer las simulaciones entre pruebas. Esto es necesario porque tenemos dos archivos simulados para dos pruebas diferentes. La primera prueba es buscar la llamada de Apex para proporcionar seis cuentas. La segunda prueba es afirmar qué sucedería si no se encuentran cuentas. Lo último es la prueba de afirmar qué sucedería si el Apex tenía un error.
Agreguemos los archivos de datos simulados y el resto del código.
- Cree el directorio
data
en el directorio__tests__
. - Cree dos archivos en el nuevo directorio
data
denominadosgetAccountList.json
ygetAccountListNoRecords.json
. - Ingrese el código a continuación en
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" } ]
- El archivo
getAccountListNoRecords.json
se rellena con un objeto de JSON en blanco:[]
- Ahora ingrese este código entre las etiquetas
template
enwireApex.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>
- Finalice sustituyendo el código en
wireApex.js
con esto: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; } } }
Observe que solo obtenemos el métodogetAccountList
desde la clase de ApexAccountController
. Recuerde que ese método debe anotarse con@AuraEnabled(cacheable=true)
para que funcione con los LWC.@wire
lo utiliza para rellenar una función con los valoreserror
odata
devueltos. - Guarde todos los archivos y ejecute las pruebas.
- Las pruebas se aprueban.
En la siguiente unidad, aborda la simulación de otros componentes y completa las formas de probar componentes web Lightning con Jest.
Recursos
- Guías de desarrollador: Componentes web Lightning: Utilizar el servicio de red para obtener datos
- Guías de desarrollador: Componentes web Lightning: Funciones y adaptadores de red de API lightning/ui*
- Guías de desarrollador: Componentes web Lightning: Redactar pruebas de Jest para componentes web Lightning que utilizan el servicio de red
- Guía del desarrollador de la API de la interfaz de usuario: Obtener un registro
- Salesforce Extensions for Visual Studio Code: Componentes web Lightning: Pruebas
- GitHub: salesforce/wire-service-jest-util
- GitHub: trailheadapps/lwc-recipes
- GitHub: wire-service-jest-util/docs/Migrating from version 2.x to 3.x