AgentStack
SKILL verified MIT Self-run

Sf Lwc Testing

skill-jiten-singh-shahi-salesforce-claude-code-sf-lwc-testing · by jiten-singh-shahi

>-

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-jiten-singh-shahi-salesforce-claude-code-sf-lwc-testing

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Sf Lwc Testing? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

LWC Testing with Jest

LWC uses Jest as its test runner. Salesforce provides @salesforce/sfdx-lwc-jest to handle Salesforce-specific imports. Tests run in Node.js — no browser, no Salesforce org.

When to Use

  • When writing Jest unit tests for Lightning Web Components
  • When mocking Wire adapters, Apex imperative calls, or navigation in LWC tests
  • When debugging flaky or async LWC tests
  • When setting up LWC test infrastructure for a new project

@../reference/LWCPATTERNS.md @../reference/TESTINGSTANDARDS.md


Setup Procedure

Step 1 — Install

npm install --save-dev @salesforce/sfdx-lwc-jest

Step 2 — jest.config.js

const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
    ...jestConfig,
    modulePathIgnorePatterns: ['/.localdevserver'],
    testEnvironment: 'jsdom',
    testMatch: ['**/__tests__/**/*.test.js'],
    setupFiles: ['/jest.setup.js']
};

Step 3 — jest.setup.js

global.ResizeObserver = jest.fn().mockImplementation(() => ({
    observe: jest.fn(), unobserve: jest.fn(), disconnect: jest.fn()
}));

Step 4 — Test File Location

lwc/accountSearch/
    accountSearch.html
    accountSearch.js
    __tests__/
        accountSearch.test.js

Basic Component Rendering

import { createElement } from 'lwc';
import AccountSearch from 'c/accountSearch';

describe('c-account-search', () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        jest.clearAllMocks();
    });

    it('renders the search input', () => {
        const element = createElement('c-account-search', { is: AccountSearch });
        document.body.appendChild(element);

        const input = element.shadowRoot.querySelector('lightning-input[type="search"]');
        expect(input).not.toBeNull();
        expect(input.label).toBe('Search Accounts');
    });

    it('renders with public @api property', () => {
        const element = createElement('c-account-search', { is: AccountSearch });
        element.maxRecords = 25;
        document.body.appendChild(element);
        expect(element.maxRecords).toBe(25);
    });
});

Wire Adapter Mocking (Modern Pattern)

Use jest.mock() with the wire adapter directly. The deprecated registerApexTestWireAdapter pattern should not be used in new projects.

import { createElement } from 'lwc';
import AccountDetails from 'c/accountDetails';
import getAccountDetails from '@salesforce/apex/AccountsController.getAccountDetails';

jest.mock(
    '@salesforce/apex/AccountsController.getAccountDetails',
    () => ({ default: jest.fn() }),
    { virtual: true }
);

describe('c-account-details wire', () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        jest.clearAllMocks();
    });

    it('displays account name when wire returns data', async () => {
        getAccountDetails.mockResolvedValue({
            Id: '001000000000001AAA', Name: 'Acme Corporation'
        });
        const element = createElement('c-account-details', { is: AccountDetails });
        element.recordId = '001000000000001AAA';
        document.body.appendChild(element);

        await Promise.resolve();
        await Promise.resolve();

        expect(element.shadowRoot.querySelector('.account-name').textContent)
            .toBe('Acme Corporation');
    });

    it('displays error state when wire returns error', async () => {
        getAccountDetails.mockRejectedValue({
            body: { message: 'Record not found' }, status: 404
        });
        const element = createElement('c-account-details', { is: AccountDetails });
        element.recordId = '001000000000001AAA';
        document.body.appendChild(element);

        await Promise.resolve();
        await Promise.resolve();

        expect(element.shadowRoot.querySelector('.error-container')).not.toBeNull();
    });
});

Apex Imperative Call Mocking

jest.mock(
    '@salesforce/apex/AccountSearchController.searchAccounts',
    () => jest.fn(),         // imperative: module IS the function
    { virtual: true }
);

import searchAccounts from '@salesforce/apex/AccountSearchController.searchAccounts';

it('calls Apex on search and displays results', async () => {
    searchAccounts.mockResolvedValue([
        { Id: '001000000000001AAA', Name: 'Acme Corp' }
    ]);
    const element = createElement('c-account-search', { is: AccountSearch });
    document.body.appendChild(element);

    // Trigger search
    const input = element.shadowRoot.querySelector('lightning-input');
    input.dispatchEvent(new CustomEvent('change', { detail: { value: 'Acme' } }));
    element.shadowRoot.querySelector('lightning-button[label="Search"]').click();

    await flushPromises();

    expect(searchAccounts).toHaveBeenCalledWith({ searchTerm: 'Acme' });
    const rows = element.shadowRoot.querySelectorAll('.account-row');
    expect(rows).toHaveLength(1);
});

Key distinction: for imperative Apex, mock as () => jest.fn(). For wired Apex, mock as () => ({ default: jest.fn() }).


Async Testing — flushPromises

LWC re-renders are asynchronous. Use flushPromises instead of chaining multiple Promise.resolve() calls.

function flushPromises() {
    return new Promise(resolve => setTimeout(resolve, 0));
}

User Interaction Simulation

Click Events and Custom Event Assertions

it('dispatches select event when button clicked', () => {
    const element = createElement('c-account-card', { is: AccountCard });
    element.account = { Id: '001000000000001AAA', Name: 'Test Corp' };
    document.body.appendChild(element);

    const handler = jest.fn();
    element.addEventListener('accountselect', handler);

    element.shadowRoot.querySelector('[data-id="view-btn"]').click();

    expect(handler).toHaveBeenCalledTimes(1);
    expect(handler.mock.calls[0][0].detail).toEqual({
        accountId: '001000000000001AAA', accountName: 'Test Corp'
    });
});

Input Value Changes

const input = element.shadowRoot.querySelector('lightning-input[type="search"]');
input.dispatchEvent(new CustomEvent('change', { detail: { value: 'Acme' } }));
await Promise.resolve();

Toast Notification Testing

import { ShowToastEventName } from 'lightning/platformShowToastEvent';

it('shows success toast after save', async () => {
    const toastHandler = jest.fn();
    element.addEventListener(ShowToastEventName, toastHandler);

    element.shadowRoot.querySelector('[data-id="save-btn"]').click();
    await flushPromises();

    expect(toastHandler).toHaveBeenCalledTimes(1);
    expect(toastHandler.mock.calls[0][0].detail.variant).toBe('success');
});

Navigation Mocking

it('navigates to record page on view', () => {
    const { navigate } = require('lightning/navigation');
    const element = createElement('c-account-card', { is: AccountCard });
    element.account = { Id: '001000000000001AAA' };
    document.body.appendChild(element);

    element.shadowRoot.querySelector('[data-id="view"]').click();

    expect(navigate).toHaveBeenCalledWith(
        expect.objectContaining({
            type: 'standard__recordPage',
            attributes: expect.objectContaining({ recordId: '001000000000001AAA' })
        })
    );
});

What to Test vs What Not to Test

Test These

  • Business logic in computed properties and event handlers
  • Correct Apex method called with correct arguments
  • Component response to wire data and errors
  • User interactions and their effects on component state
  • Edge cases: empty arrays, null values, error states

Do Not Test These

  • LWC framework behavior (that for:each renders items)
  • Base component rendering (that lightning-button shows text)
  • CSS styling or implementation details (internal variable names)

Spring '26: Local Component Testing

Test LWC components in isolation without deploying to a scratch org.

npm install --save-dev @lwc/jest-preset
npx lwc-jest --watchAll=false

TypeScript LWC Testing (Spring '26 Experimental)

Spring '26 introduces experimental TypeScript support for LWC. Test .ts component files with these adjustments.

Jest Configuration

// jest.config.js — add ts transform
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
    ...jestConfig,
    transform: {
        ...jestConfig.transform,
        '^.+\\.ts$': ['@swc/jest']  // or 'ts-jest'
    },
    moduleFileExtensions: ['ts', 'js', 'html'],
    testMatch: ['**/__tests__/**/*.test.(js|ts)']
};

Install: npm install --save-dev @swc/jest @swc/core (faster) or npm install --save-dev ts-jest typescript.

Typed Wire Adapter Mocking

import { createElement } from 'lwc';
import AccountList from 'c/accountList';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

jest.mock('@salesforce/apex/AccountController.getAccounts',
    () => ({ default: jest.fn() }), { virtual: true });

const mockGetAccounts = getAccounts as jest.MockedFunction;

describe('c-account-list (TypeScript)', () => {
    afterEach(() => {
        while (document.body.firstChild) document.body.removeChild(document.body.firstChild);
        jest.clearAllMocks();
    });

    it('renders typed account data', async () => {
        mockGetAccounts.mockResolvedValue([
            { Id: '001xx0001', Name: 'Typed Corp', Industry: 'Technology' }
        ]);
        const element = createElement('c-account-list', { is: AccountList });
        document.body.appendChild(element);

        await Promise.resolve();
        await Promise.resolve();

        const rows = element.shadowRoot.querySelectorAll('.account-row');
        expect(rows).toHaveLength(1);
    });
});

Typed Event Detail Assertions

it('dispatches typed custom event', () => {
    const element = createElement('c-account-card', { is: AccountCard });
    document.body.appendChild(element);

    const handler = jest.fn();
    element.addEventListener('select', handler);
    element.shadowRoot.querySelector('[data-id="select-btn"]').click();

    const detail = handler.mock.calls[0][0].detail as { accountId: string };
    expect(detail.accountId).toBeDefined();
});

> Note: TypeScript LWC support is experimental in Spring '26. Type definitions and tooling may change in future releases. Pin @salesforce/sfdx-lwc-jest to a known-good version.


Related

Guardrails

  • sf-testing-constraints — Enforced rules for test coverage, assertions, and test data patterns

Agents

  • sf-lwc-agent — For interactive, in-depth LWC review guidance

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.