Skip to main content

testing

Write unit and integration tests for the Ledger Wallet apps and for packages in the layers (`shared/`, `domain/`, `features/`, `devtools/`): Jest with React Testing Library, `@support/jest` presets, MSW handlers and testing best practices. Integration tests fake HTTP with MSW `jest.fn()` endpoint mocks instead of `jest.mock`-ing modules. Use when writing or reviewing "*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**", "**/__integrations__/**", "**/jest-setup*" or a package's `jest.config.js`.

Source facts

Repository
LedgerHQ/ledger-live
Last source activity
October 31, 2026 at 02:08
Detected SKILL.md language
English
Stars
624
Forks
496

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
testing
description
Write unit and integration tests for the Ledger Wallet apps and for packages in the layers (`shared/`, `domain/`, `features/`, `devtools/`): Jest with React Testing Library, `@support/jest` presets, MSW handlers and testing best practices. Integration tests fake HTTP with MSW `jest.fn()` endpoint mocks instead of `jest.mock`-ing modules. Use when writing or reviewing "*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**", "**/__integrations__/**", "**/jest-setup*" or a package's `jest.config.js`.
# Ledger Wallet Testing Skill > Jest + MSW + React Testing Library (Desktop) / React Native Testing Library (Mobile) > ❌ E2E tests → Use `e2e-desktop-add-or-update` / `e2e-mobile-add-or-update` skills > ⚠️ A new desktop flow needs Jest tests **and** an `e2e/desktop` spec. `apps/ledger-live-desktop/tests/specs` (mocked Playwright) is frozen: fix, update or delete, never add a spec file or test case. ## Golden Rules 1. **`toBeVisible()` over `toBeInTheDocument()`** — Always. `toBeInTheDocument` only checks DOM presence; elements can be hidden. Use `toBeInTheDocument` only when testing explicitly hidden elements. 2. **Search before you create** — Before writing any mock, fixture, or helper, `rg` the codebase. If it exists, import it. If your new mock is reusable (2+ files), put it in a shared location. 3. **Fake the network, not modules** — Integration tests run the real store, API clients and SDKs, and intercept HTTP with MSW [endpoint mocks](references/msw-endpoints.md). `jest.mock` only what cannot run under Jest (native modules, device transport). Never mock child components. 4. **Network requests via MSW, never `fetch`** — New test code must not mock `fetch`; use Mock Service Worker handlers instead. 5. **One behavior per test** — Name: `it("should <behavior> when <condition>")`. 6. **Query priority**: `getByRole` > `getByLabelText` > `getByText` > `getByTestId` (last resort). 7. **Feature flags via store, never mocked**: `withFlagOverrides()` sets `state.featureFlags.overrides`. 8. **Use existing factories** — `genAccount()` from `@ledgerhq/ledger-wallet-framework/mocks/account`, `getCryptoCurrencyById()` from `@ledgerhq/live-common/currencies`. Never recreate account/currency data from scratch. ## Quick Reference ```bash pnpm test:jest "filename" # Run specific file pnpm test:jest # Run all tests pnpm test:jest --coverage # Coverage report # Packages in the layers pnpm nx run <package-name>:test # Coin modules (libs/coin-modules) pnpm coin:<name> test # Unit tests pnpm coin:<name> test "filename" # Specific file pnpm coin:<name> test-integ # Integration tests (slow) ``` | Platform | Render Import | MSW Server | | -------- | ---------------------- | --------------------- | | Desktop | `tests/testSetup` | `tests/server.ts` | | Mobile | `@tests/test-renderer` | `__tests__/server.ts` | ## Test Template ```typescript import { render, screen, waitFor } from "tests/testSetup"; import { HttpResponse, http, server } from "tests/server"; const endpoints = { data: jest.fn<Response, [{ request: Request }]>() }; describe("MyComponent", () => { beforeEach(() => { jest.clearAllMocks(); endpoints.data.mockReset().mockImplementation(() => HttpResponse.json({ name: "Bitcoin" })); server.use(http.get("/api/data", endpoints.data)); }); it("should render title", () => { render(<MyComponent title="Test" />); expect(screen.getByText("Test")).toBeVisible(); }); it("should call onClick when button pressed", async () => { const onClick = jest.fn(); const { user } = render(<MyComponent onClick={onClick} />); await user.click(screen.getByRole("button")); expect(onClick).toHaveBeenCalledTimes(1); }); it("should display fetched data", async () => { render(<MyComponent />); await waitFor(() => expect(screen.getByText("Bitcoin")).toBeVisible()); expect(endpoints.data).toHaveBeenCalledTimes(1); }); it("should display an error when the API fails", async () => { endpoints.data.mockReturnValueOnce(HttpResponse.json({}, { status: 500 })); render(<MyComponent />); // ... }); }); ``` Full recipe, traps and examples: [references/msw-endpoints.md](references/msw-endpoints.md). ## Testing Hooks ```typescript import { renderHook, act, waitFor } from "tests/testSetup"; const { result } = renderHook(() => useMyHook()); await act(async () => result.current.increment()); expect(result.current.value).toBe(1); ``` ## Redux Store **Desktop** — plain object merged with defaults: ```typescript render(<MyComponent />, { initialState: { settings: { theme: "dark" }, accounts: { active: [mockAccount] }, }, }); ``` **Mobile** — function receiving default state: ```typescript render(<MyScreen />, { overrideInitialState: (state: State) => ({ ...state, settings: { ...state.settings, blacklistedTokenIds: ["ethereum/erc20/usdt"] }, }), }); ``` **Feature flags**: local overrides live in `state.featureFlags.overrides`, and `useFeature` reads `state.featureFlags.resolved`. `withFlagOverrides` (exported by both test renderers) fills both, merged over the flag's defaults: ```typescript // Desktop: import { render, withFlagOverrides } from "tests/testSetup"; render(<MyComponent />, { initialState: withFlagOverrides({ myFlag: { enabled: true, params: { key: "value" } } }), }); // Mobile: import { render, withFlagOverrides } from "@tests/test-renderer"; render(<MyScreen />, { overrideInitialState: withFlagOverrides({ myFlag: { enabled: true } }), }); ``` ## Mocking Patterns What to `jest.mock` and when, the shared app mocks to search first, and the anti-patterns that make parallel runs flaky: [references/mocks.md](references/mocks.md). ## Packages in the Layers Packages under `shared/`, `domain/`, `features/` and `devtools/` have no app harness. Each one: - builds its `jest.config.js` from a `@support/jest` preset (`shared`, `features-flow`, `devtools`, `libs-coin`) with the default call: `createSharedJestConfig({ rootDir: __dirname })` for node code, `createFlowJestConfig()` for a flow. Options and overrides: `support/jest/README.md`. - splits UI tests into a `web` and a `native` jest project by suffix: `X.web.test.tsx`, `X.native.test.tsx`. - keeps tests next to the code in `src/`, and mock factories in `*.mock.ts` out of the barrel. - fakes its endpoints with the MSW handlers its `domain/api/*` package ships (`@domain/api-card-management/mock/card-api`). Presets, projects, layout and domain handlers: [references/packages.md](references/packages.md). ## Workflow ``` 1. Search for existing mocks → 2. Write test → 3. Run test → 4. Fix if fail → 5. Next file ``` Start with the simplest units (utils, hooks), then work up to components with side effects (API, navigation). This order catches foundational bugs early and builds confidence incrementally.
View on GitHub