4

In order to fully explain what I mean here, I have a proof of concept small component written. Although the loading element is already set to be false according to console.log and in the correct execution sequence, the html part is not displayed quite well.

In my lwc html:

<template>
    <template if:true={loading}>
    <div>
      loading section
    </div>
  </template>
  <template if:false={loading}>
    <p>
        actual part
    </p>
  </template>
</template>

And in my lwc javascript file:

import { LightningElement, wire, track, api } from 'lwc';
import { publish, subscribe, MessageContext } from "lightning/messageService";
import UpdateAccountsAndGoals from "@salesforce/messageChannel/FinancialAccountsGoalsUpdate__c";
import getTotalBalance from "@salesforce/apex/TotalBalanceController.getTotalBalance";

export default class SamplePubSub extends LightningElement {
    @wire(MessageContext)
  messageContext;
  subscription = null;
  @track loading;
  @api recordId;

    connectedCallback() {
        this.loading = true;
      this.subscription = subscribe(
      this.messageContext,
      UpdateAccountsAndGoals,
      (message) => {
        if (message.update) {
          this.getTotal();
          console.log('In message subscribe');
          console.log(this.loading);
        } else {

        }
      }
    );
    }

  getTotal() {
    getTotalBalance({
      ownerId: this.recordId
    })
      .then((result) => {
        this.loading = false;
        console.log('loading value');
        console.log(this.loading);
      })
      .catch((error) => {
      });
  }
}

In my jest code:

import samplePubSub from "c/samplePubSub";
import { createElement } from "lwc";
import { publish, subscribe, MessageContext } from "lightning/messageService";
import {
    registerLdsTestWireAdapter,
    registerTestWireAdapter
} from '@salesforce/sfdx-lwc-jest';
import UpdateAccountsAndGoals from "@salesforce/messageChannel/FinancialAccountsGoalsUpdate__c";
import getTotalBalance from "@salesforce/apex/TotalBalanceController.getTotalBalance";

const messageContextWireAdapter = registerTestWireAdapter(MessageContext);

jest.mock(
  "@salesforce/apex/TotalBalanceController.getTotalBalance",
  () => {
    return {
      default: jest.fn()
    };
  },
  { virtual: true }
);

const APEX_TOTAL_BALANCE_SUCCESS = {
    totalBalance: 123.21,
};

describe("c-samplePubSub", () => {
  //clean the dom and mocks in between test runs
  afterEach(() => {
    while (document.body.firstChild) {
      document.body.removeChild(document.body.firstChild);
    }
    jest.clearAllMocks();
  });

  it("test pub sub", () => {
    getTotalBalance.mockResolvedValue(APEX_TOTAL_BALANCE_SUCCESS);
    const element = createElement('c-samplePubSub', {
        is: samplePubSub
    });
    document.body.appendChild(element);
    
    const payload = {
      update: true
    };
    publish(messageContextWireAdapter, UpdateAccountsAndGoals, payload);

    return Promise.resolve().then(() => {
      const mainEle = element.shadowRoot.querySelector('p');
      console.log(111);
      expect(mainEle).not.toBeNull();
    });
  });
});

I wrote everything as simple as it could. If I execute this test code, it will be failing due to the p element is actually null.

If I look at the console.log sequence, it shows that this.loading is already set to be false before query the "p" element, which means the execution order is correct.

However, it still shows that the p element is not there in the template yet.

How should I walk around this issue?

1 Answer 1

3

Because you are using apex imperatively the DOM updates are not reflected just by resolving the Promise. There is a micro-task queue still holding up a few things. Note this is not needed if your apex was using the wire adaptor.

One common technique to use in Test execution is to make sure you manually complete all the asynchronous operations.

Use the setImmediate for this. This is also explained here

Here is how I got this working,

Create a helper function like below to make sure all promises are resolved.

// Helper function to wait until the microtask queue is empty. This is needed for the promise
// timing when calling imperative Apex.
   function flushPromises() {
      // eslint-disable-next-line no-undef
     return new Promise((resolve) => setImmediate(resolve));
   }

Complete working code

import samplePubSub from "c/lmsTest";
import { createElement } from "lwc";
import { publish, subscribe, MessageContext } from "lightning/messageService";
import {
   registerLdsTestWireAdapter,
   registerTestWireAdapter
} from '@salesforce/sfdx-lwc-jest';
import UpdateAccountsAndGoals from 
"@salesforce/messageChannel/FinancialAccountsGoalsUpdate__c";
import getTotalBalance from 
 "@salesforce/apex/TotalBalanceController.getTotalBalance";

const messageContextWireAdapter = registerTestWireAdapter(MessageContext);

jest.mock(
  "@salesforce/apex/TotalBalanceController.getTotalBalance",
   () => {
     return {
     default: jest.fn()
    };
  },
 { virtual: true }
 );

const APEX_TOTAL_BALANCE_SUCCESS = {
   totalBalance: 123.21,
};

 describe("c-samplePubSub", () => {
  //clean the dom and mocks in between test runs
   afterEach(() => {
     while (document.body.firstChild) {
      document.body.removeChild(document.body.firstChild);
   }
    jest.clearAllMocks();
  });

// Helper function to wait until the microtask queue is empty. This is needed for the promise
  // timing when calling imperative Apex.
   function flushPromises() {
     // eslint-disable-next-line no-undef
     return new Promise((resolve) => setImmediate(resolve));
   }

  it("test pub sub", () => {
     getTotalBalance.mockResolvedValue(APEX_TOTAL_BALANCE_SUCCESS);
     const element = createElement('c-samplePubSub', {
        is: samplePubSub
      });
    document.body.appendChild(element);

    const payload = {
       update: true
    };
    publish(messageContextWireAdapter, UpdateAccountsAndGoals, payload);

   return flushPromises().then(() => {
      const mainEle = element.shadowRoot.querySelector('p');
      console.log(mainEle);
      expect(mainEle).not.toBeNull();
      });
   });
});

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .