Install
$ agentstack add skill-jiten-singh-shahi-salesforce-claude-code-sf-lwc-testing ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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:eachrenders items) - Base component rendering (that
lightning-buttonshows 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.
- Author: jiten-singh-shahi
- Source: jiten-singh-shahi/salesforce-claude-code
- License: MIT
- Homepage: https://www.npmjs.com/package/scc-universal
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.