Skip to main content

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étodo getRecord importado y aprobando en la variable $recordId reactiva como su primer argumento. El segundo argumento es una referencia al Account.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.

  1. En Visual Studio Code, haga clic con el botón derecho en la carpeta lwc y seleccione SFDX: Crear componente web Lightning.
  2. Ingrese wireCPR como nombre para el nuevo componente.
  3. Pulse Entrar.
  4. Pulse Entrar para aceptar la ubicación force-app/main/default/lwc.
  5. En la nueva carpeta wireCPR/__tests__, abra el archivo wireCPR.test.js.
  6. 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)
          );
        });
      });
    });
  7. 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.

  1. Haga clic con el botón derecho en el directorio __tests__ y seleccione Nueva carpeta.
  2. Ingrese data para el nombre del nuevo directorio.
  3. Pulse Entrar.
  4. Haga clic con el botón derecho en el directorio data y seleccione Nuevo archivo.
  5. Ingrese CurrentPageReference.json.
  6. Pulse Entrar.
  7. Ingrese el siguiente bloque de código de json en el nuevo archivo:
    {
      "type": "standard__navItemPage",
      "attributes": {
        "apiName": "Wire"
      },
      "state": {}
    }
  8. Guarde el archivo y ejecute las pruebas.
  9. 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.

  1. Abra wireCPR.html.
  2. Agregue el siguiente código en las etiquetas template:
      <lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67">
        <pre>{currentPageRef}</pre>
      </lightning-card>
  3. Guarde el archivo.
  4. 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) : '';
      }
    }
  5. Guarde el archivo y ejecute las pruebas.
  6. 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.

  1. Cree un nuevo componente web Lightning en Visual Studio Code.
  2. Establezca el nombre como wireLDS.
  3. 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
            );
          });
        });
      });
    });
  4. Guarde el archivo y ejecute las pruebas.
  5. 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 directorio data.
  • Línea 23 utiliza el método emit en getRecord con mockGetRecord 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.

  1. Cree el directorio data en el directorio __tests__.
  2. Cree el archivo de datos de prueba con el nombre getRecord.json.
  3. 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"
    }
  4. Guarde el archivo y ejecute las pruebas.
  5. La prueba falla.
  6. 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>
  7. Guarde el archivo y ejecute las pruebas.
  8. La prueba falla de nuevo, pero ya casi terminamos. Solo necesita agregar el controlador de JavaScript para obtener los datos.
  9. 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);
      }
    }
  10. Guarde el archivo y ejecute las pruebas.
  11. Las pruebas se aprueban.
Nota

Nota

Los componentes web Lightning acceden a metadatos y datos de Salesforce desde todos los objetos estándar y personalizados compatibles con la API de interfaz de usuario. Los objetos externos no son compatibles.

Obtenga datos de prueba acaparando una instantánea de datos utilizando un cliente de REST para acceder a la API de interfaz de usuario. Este enfoque es más preciso que redactar el JSON a mano. Este es un ejemplo de la llamada de REST para obtener los datos anteriores (necesitará su propio Id. de cuenta):

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

¿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.

  1. 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.');
          });
        });
      });
  2. Guarde el archivo y ejecute las pruebas.
  3. Las pruebas se aprueban porque está utilizando el método error() en el getRecordAdapter. Esto causa un error en los datos simulados de modo que account.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.

  1. Cree un nuevo componente web Lightning en Visual Studio Code.
  2. Establezca el nombre como wireApex.
  3. 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.');
          });
        });
      });
    });
  4. Guarde el archivo y ejecute las pruebas.
  5. 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.

  1. Cree el directorio data en el directorio __tests__.
  2. Cree dos archivos en el nuevo directorio data denominados getAccountList.json y getAccountListNoRecords.json.
  3. 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"
      }
    ]
  4. El archivo getAccountListNoRecords.json se rellena con un objeto de JSON en blanco:
    []
  5. Ahora ingrese este código entre las etiquetas template en 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. 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étodo getAccountList desde la clase de Apex AccountController. 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 valores error o data devueltos.
  7. Guarde todos los archivos y ejecute las pruebas.
  8. 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

Comparta sus comentarios de Trailhead en la Ayuda de Salesforce.

Nos encantaría saber más sobre su experiencia con Trailhead. Ahora puede acceder al nuevo formulario de comentarios en cualquier momento en el sitio de Ayuda de Salesforce.

Más información Continuar a Compartir comentarios