Skip to main content

Escrever um teste do Jest para o serviço de conexão

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:

  • Listar os três adaptadores primários para serviços de conexão.
  • Explicar os dados de simulação para o serviço de conexão.
  • Entender as variáveis reativas e seus efeitos.

Testando o serviço @wire

Os componentes web do Lightning utilizam um serviço de conexão reativo criado no Lightning Data Service para ler os dados do Salesforce. Os componentes utilizam @wire em sua classe JavaScript para ler os dados de um dos adaptadores de conexão nos módulos lightning/ui*Api.

O serviço de conexão é parcialmente reativo porque suporta variáveis reativas. As variáveis reativas são prefixadas com um $. Quando uma variável reativa muda, o serviço de conexão fornece novos dados. Se os dados existirem no cache do cliente, uma solicitação de rede pode não estar envolvida.

Usamos o utilitário de teste @salesforce/sfdx-lwc-jest para testar como esses componentes tratam dados e erros do serviço de conexão.

O teste exige que você tenha total controle sobre a entrada que seu teste consome. Nenhum código externo ou dependências de dados são permitidos. Importamos a API de utilitário de teste de sfdx-lwc-jest para simular os dados; assim, nosso teste não ficará dependente de fatores imprevisíveis como invocação remota ou latência do servidor.

Existem três adaptadores para simular dados de serviço de conexão.

  • Adaptador de conexão genérico: O adaptador genérico emite dados sob demanda quando a API emit() é acionada. Ele não inclui informações adicionais sobre os próprios dados.
  • Adaptador de conexão do Lightning Data Service (LDS): O adaptador LDS imita o comportamento do Lightning Data Service e inclui informações sobre as propriedades dos dados.
  • Adaptador de conexão do Apex: O adaptador de conexão do Apex imita chamadas para um método do Apex e inclui qualquer status de erro.

Vamos analisar um decorador @wire típico. Importe um adaptador de conexão usando a sintaxe de importação nomeada. Decore uma propriedade ou função com @wire e especifique o adaptador de conexão. Cada adaptador de conexão define um tipo de dados.

Esse código importa o campo Account.Name e o utiliza no objeto de configuração de um adaptador de conexão.

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;
}

Vamos analisar mais detalhadamente.

  • A linha 8 está usando o decorador @wire para acessar o método getRecord importado e passar a variável $recordId reativa como seu primeiro argumento. O segundo argumento é uma referência ao Account.Name importado do esquema na linha 3.
  • A linha 9 pode ser uma função ou propriedade privada que recebe o fluxo de dados do serviço de conexão. Se for uma propriedade, os resultados são retornados para a propriedade de dados ou propriedade de erro da propriedade. Se for uma função, os resultados serão retornados em um objeto com uma propriedade de dados e uma propriedade de erro.

Agora vamos dar uma olhada nos diferentes adaptadores.

Usando o adaptador de conexão genérico

Primeiro, usamos o serviço @wire com CurrentPageReference.

O serviço lightning-navigation oferece adaptadores e funções de conexão para gerar um URL ou navegar até uma referência de página. Vamos usar CurrentPageReference para obter uma referência até a página atual no Salesforce e criar um teste.

  1. No Visual Studio Code, clique com o botão direto na pasta LWC e selecione SFDX: Criar componente web do Lightning.
  2. Digite wireCPR como nome do novo componente.
  3. Pressione Enter.
  4. Pressione Enter novamente para aceitar o local force-app/main/default/lwc padrão.
  5. Na nova pasta wireCPR/__tests__, abra o arquivo wireCPR.test.js.
  6. Substitua o novo arquivo por:
    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. Salve o arquivo e execute os testes.

Vamos analisar mais detalhadamente.

  • A linha 3 tem uma nova importação: CurrentPageReference.
  • A linha 6 captura um arquivo com dados simulados de PageReference. Ainda não criamos isso, então essa é a primeira razão para o erro do teste.
    Test suite failed to run
      Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
    Corrigiremos o problema em seguida.
  • É na linha 26 que preenchemos os dados simulados usando emit().
  • A linha 28 inicia a Promessa que espera que os dados simulados sejam atualizados no preElement.

Vamos criar o arquivo de dados de teste e atualizar o código para que o teste seja aprovado. Primeiro, crie um novo diretório no diretório __tests__ para armazenar o arquivo de dados simulado.

  1. Clique com o botão direito do mouse no diretório __tests__ e selecione Nova pasta.
  2. Digite data como nome do novo componente.
  3. Pressione Enter.
  4. Clique com o botão direito do mouse no diretório data e selecione Novo arquivo.
  5. Insira CurrentPageReference.json.
  6. Pressione Enter.
  7. Insira o bloco de código json a seguir no novo arquivo:
    {
      "type": "standard__navItemPage",
      "attributes": {
        "apiName": "Wire"
      },
      "state": {}
    }
  8. Salve o arquivo e execute os testes.
  9. O teste recebe essa mensagem de erro.
    expect(received).not.toBeNull()
      Received: null
    Excelente. Mesmo um teste com falha pode promover o progresso ao identificar rapidamente qualquer problema enquanto você trabalha com o código.

Em seguida, adicionamos o código HTML e JavaScript.

  1. Abra wireCPR.html.
  2. Adicione o código a seguir dentro das marcas template:
      <lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67">
        <pre>{currentPageRef}</pre>
      </lightning-card>
  3. Salve o arquivo.
  4. Abra wireCPR.js e substitua o código pelo seguinte:
    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. Salve o arquivo e execute os testes.
  6. Os testes passam.

Vamos conferir o que está acontecendo. Quando o adaptador @wire é usado, busca informações retornadas de um serviço. Precisamos criar uma simulação desses dados para utilizar em vez de fazer a chamada para o serviço a fim de obter os dados. Assim, continuamos a testar apenas os itens que temos atualmente e não aspectos fora de nosso escopo. Isso também ajuda a manter a rapidez dos testes.

Usando o adaptador de conexão do Lightning Data Service

Em seguida, utilizamos @wire com o Lightning Data Service (LDS). O LDS concede acesso rápido a objetos personalizados e padrão. Nosso componente obtém dados do Salesforce usando o LDS e os exibe. Criaremos o teste para verificar se os dados são exibidos como esperado usando o adaptador LDS.

  1. Crie um novo componente web do Lightning no Visual Studio Code.
  2. Defina o nome como wireLDS.
  3. Substitua o código no arquivo de teste 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. Salve o arquivo e execute os testes.
  5. O teste falha devido à falta do arquivo de dados simulado que criamos a seguir.

Antes disso, vamos analisar o código de teste para ver o que está acontecendo.

  • A linha 3 tem uma nova importação: getRecord. getRecord está vindo da API LDS.
  • A linha 6 está simulando novamente os dados do arquivo getRecord.json no diretório data.
  • A linha 23 usa o método emit em getRecord com mockGetRecord como um argumento.
  • A linha 25 inicia o retorno da Promessa e conferimos que vários elementos são atualizados com os dados simulados.

Em seguida, criamos o arquivo de dados simulado e o restante dos arquivos para obter um teste que seja aprovado. Executamos os testes depois que cada arquivo é criado para ver a progressão dos erros de teste até que sejam aprovados.

  1. Crie o diretório data no diretório __tests__.
  2. Crie o arquivo de dados de teste com o nome getRecord.json.
  3. Adicione o código a seguir:
    {
      "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. Salve o arquivo e execute os testes.
  5. O teste falha.
  6. Abra wireLDS.html e insira o código a seguir entre as marcas 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. Salve o arquivo e execute os testes.
  8. O teste falha novamente, mas estamos quase lá. Você só precisa adicionar o controlador JavaScript para obter os dados.
  9. Abra wireLDS.js e substitua todo o código por:
    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. Salve o arquivo e execute os testes.
  11. Os testes passam.
Nota

Nota

Os componentes web do Lightning acessam dados e metadados do Salesforce a partir de todos os objetos personalizados e padrão compatíveis com a API de interface do usuário. Não há suporte para objetos externos.

Obtenha dados de teste ao capturar um instantâneo de dados utilizando um cliente REST para acessar a API de interface do usuário. Essa abordagem é mais precisa do que escrever JSON manualmente. Aqui está um exemplo da chamada REST para obter os dados acima (você precisará do seu próprio ID de conta):

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

Mas e se houver um erro ao obter os dados? Também é possível testá-lo. Vamos adicionar um novo bloco describe em nosso arquivo de teste wireLDS.test.js.

  1. Adicione o código a seguir logo após o bloco describe “getRecord @wire data” para que esteja dentro do bloco describe “c-wire-l-d-s”. É possível aninhar blocos describe para ajudar a clarificar os testes.
      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. Salve o arquivo e execute os testes.
  3. Os testes passam porque o método error() está sendo utilizado no getRecordAdapter. Isso gera um erro nos dados simulados para que account.error seja verdadeira.

Usando o adaptador de conexão do Apex

Em seguida, vamos nos aprofundar no Apex e ver como podemos usar @wire para testá-lo.

A classe do Apex que o LWC está importando é considerada uma conexão externa que precisará ser simulada. Isso significa que podemos testar sem precisar criar a classe do Apex. Tudo o que precisamos fazer é simular a resposta esperada da chamada do Apex. Neste caso, estamos esperando exibir contas que são retornadas da classe do Apex. Criaremos testes que esperem que as contas sejam exibidas quando retornadas, ou uma mensagem se nenhuma for retornada.

Vamos criar o LWC que o utiliza.

  1. Crie um novo componente web do Lightning no Visual Studio Code.
  2. Defina o nome como wireApex.
  3. Substitua o código no arquivo de teste 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. Salve o arquivo e execute os testes.
  5. Você recebe um erro para o arquivo de dados simulado ausente.

A maior parte do código é familiar. Há um novo item, jest.clearAllMocks(), no código de limpeza para redefinir as simulações entre os testes. Isso é necessário porque temos dois arquivos fictícios para dois testes diferentes. O primeiro teste procura a chamada do Apex para enviar seis contas. O segundo teste declara o que acontece se nenhuma conta for encontrada. O último é o teste para declarar o que acontece se o Apex tiver um erro.

Vamos adicionar os arquivos de dados simulados e o restante do código.

  1. Crie o diretório data no diretório __tests__.
  2. Crie dois arquivos no novo diretório data chamados getAccountList.json e getAccountListNoRecords.json.
  3. Insira o código abaixo em 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. O arquivo getAccountListNoRecords.json é preenchido com um objeto JSON em branco:
    []
  5. Agora insira esse código entre as marcas template em 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. Conclua ao substituir o código no wireApex.js com o seguinte:
    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 estamos obtendo apenas o método getAccountList da classe do Apex AccountController. Lembre-se: esse método precisa ser anotado com @AuraEnabled(cacheable=true) para que funcione com LWCs. O @wire o utiliza para preencher uma função com error ou data retornados.
  7. Salve todos os arquivos e execute os testes.
  8. Os testes passam.

Na próxima unidade, você abordará a simulação de outros componentes e concluirá as maneiras de testar os componentes web do Lightning com Jest.

Recursos

Compartilhe seu feedback do Trailhead usando a Ajuda do Salesforce.

Queremos saber sobre sua experiência com o Trailhead. Agora você pode acessar o novo formulário de feedback, a qualquer momento, no site Ajuda do Salesforce.

Saiba mais Continue compartilhando feedback