Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

Test what the component does, not what it renders. Assert on the events it fires, the Apex it calls with which arguments, and how it behaves when a wire returns an error, not on class names and DOM structure, which change every time a designer touches the template. A suite full of markup assertions is a suite that fails on every restyle and catches nothing.

Which LWC tests earn their keepJest coverage does not count toward deployment, so these tests only survive if they are useful.Assert on behaviourThese fail when something is actually brokenThe event fired, and what it carriedApex called, with which argumentsThe error path renders a message, not ablank panelThe value the user came for appearsDo not assert on markupThese fail when a designer changes a classClass names and SLDS structureWhat a child component did internallyWhole-template snapshots, updated withoutreadingThat the framework's own directives workEmit through the wire adapter's test utility rather than mocking the module — mocking skips thereactive path, which is the thing you wanted to test.
Test the behaviour. Markup assertions fail on every restyle.

What Jest actually gives you in LWC

@salesforce/sfdx-lwc-jest runs your components in a simulated DOM with the framework’s own test utilities. Apex methods, wire adapters and navigation are all stubbed for you, so a test never touches an org and runs in milliseconds.

That speed is the point. A test that runs in 40 milliseconds gets run on every save. One that needs a deployment gets run on Fridays.

The four tests worth writing for almost any component

1. It renders the data it was given. One assertion, not twenty — that the value appears, not the exact markup around it.

2. It fires the right event. This is a contract with the parent, and contracts deserve tests:

it('fires selected with the record id', () => {
    const element = createElement('c-row', { is: Row });
    element.record = { Id: '001000000000001' };
    document.body.appendChild(element);

    const handler = jest.fn();
    element.addEventListener('selected', handler);
    element.shadowRoot.querySelector('button').click();

    expect(handler).toHaveBeenCalled();
    expect(handler.mock.calls[0][0].detail.recordId).toBe('001000000000001');
});

3. It calls Apex with the arguments you think. The stub records the call, so assert on it. Half the bugs in a component are a parameter named differently on each side.

4. It survives an error. Emit an error through the wire adapter and assert the component shows a message rather than a blank panel. This is the test that finds real defects, because nobody clicks through the error path by hand.

Testing wires: emit, do not mock the module

The framework gives each wire adapter a test utility that pushes data or an error into the component the way the platform would:

import { getRecord } from 'lightning/uiRecordApi';

// success
getRecord.emit({ fields: { Name: { value: 'Sample Industries' } } });

// failure
getRecord.error({ status: 400, body: { message: 'Field not accessible' } });

Mocking the import instead is tempting and skips the thing you wanted to test — the reactive path. Emitting exercises it.

One detail that trips everyone once: emitting is asynchronous with respect to rendering. Await a microtask before asserting, or your assertion runs against the previous frame and the test fails for a reason that has nothing to do with your code.

What not to assert

  • Class names and SLDS structure. They change for visual reasons and break tests that were never about visuals.
  • Child component internals. Assert that you passed the right property, not what the child did with it, that is the child’s test.
  • Snapshot everything. A snapshot of a whole template is a test that fails loudly on every change and is updated without reading. One or two snapshots of stable markup are fine. A suite of them is noise.
  • The framework. No test should be checking that lwc:if works.

The coverage question, answered honestly

Jest coverage is not org coverage. It does not count toward the 75% you need to deploy Apex, and nobody blocks a release on it. So the reason to write these is not compliance, which means they only survive if they are useful.

That is the argument for the four above and against exhaustive DOM testing: write the tests you would actually read a failure from. A failing test that says “selected event carried the wrong id” is worth having. One that says “expected 3 divs, found 2” gets deleted in a fortnight.

Getting it running

The setup is a dev dependency and a script, and it works locally with no org connection at all, which makes it the fastest feedback loop available in Salesforce development. Tests live beside the component in a __tests__ folder, and the CLI scaffolds the structure when you create a component.

If it is not currently in your pipeline, adding it to CI is a small job with a real payoff, and it pairs with the component communication rules those tests end up enforcing: front-end regressions that used to reach a sandbox now fail in a pull request.

Frequently asked

Do Jest tests replace Apex tests? No. They cover the component. The Apex still needs its own tests, and only those count toward deployment coverage.

Can Jest test a component against real org data? No, and that is deliberate. If you need a real org, that is a UI test with a different tool and a much slower loop.

How do I test something using Lightning Message Service? The message channel is stubbed like any other module, so publish into it and assert the subscriber reacted. Reset the mocks between tests or state leaks between them.

Is it worth retro-fitting tests to old components? Only where you are about to change one. Retro-fitting a suite to code nobody touches produces tests nobody reads.