Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Write unit and integration tests for Ledger Wallet apps. Use for Jest tests (Desktop/Mobile), MSW handlers and testing best practices. Applies to "*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**", "**/__integrations__/**", "**/jest-setup*"
.claude/skills/ledgerhq-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -4% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 22% | 0% |
> Jest + MSW + React Testing Library (Desktop) / React Native Testing Library (Mobile) > ❌ E2E tests → Use e2e skill
toBeVisible() over toBeInTheDocument() — Always. toBeInTheDocument only checks DOM presence; elements can be hidden. Use toBeInTheDocument only when testing explicitly hidden elements.rg the codebase. If it exists, import it. If your new mock is reusable (2+ files), put it in a shared location.it("should <behavior> when <condition>").getByRole > getByLabelText > getByText > getByTestId (last resort).overriddenFeatureFlags in initialState.settings.genAccount() from @ledgerhq/ledger-wallet-framework/mocks/account, getCryptoCurrencyById() from @ledgerhq/live-common/currencies. Never recreate account/currency data from scratch.bashpnpm test:jest "filename" # Run specific file pnpm test:jest # Run all tests pnpm test:jest --coverage # Coverage report # 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 |
typescriptimport { render, screen, waitFor } from "tests/testSetup"; import { server } from "tests/server"; import { http, HttpResponse } from "msw"; jest.mock("@ledgerhq/live-common/someService"); describe("MyComponent", () => { beforeEach(() => jest.clearAllMocks()); 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 () => { server.use(http.get("/api/data", () => HttpResponse.json({ name: "Bitcoin" }))); render(<MyComponent />); await waitFor(() => expect(screen.getByText("Bitcoin")).toBeVisible()); }); });
typescriptimport { renderHook, act, waitFor } from "tests/testSetup"; const { result } = renderHook(() => useMyHook()); await act(async () => result.current.increment()); expect(result.current.value).toBe(1);
Desktop — plain object merged with defaults:
typescriptrender(<MyComponent />, { initialState: { settings: { theme: "dark" }, accounts: { active: [mockAccount] }, }, });
Mobile — function receiving default state:
typescriptrender(<MyScreen />, { overrideInitialState: (state: State) => ({ ...state, settings: { ...state.settings, blacklistedTokenIds: ["ethereum/erc20/usdt"] }, }), });
Feature flags (both platforms):
typescriptrender(<MyComponent />, { initialState: { settings: { overriddenFeatureFlags: { myFlag: { enabled: true, params: { key: "value" } }, }, }, }, });
typescript// External dependency jest.mock("@ledgerhq/live-common/account/index"); const mockedFn = jest.mocked(getMainAccount); mockedFn.mockReturnValue(mockAccount); // ViewModel (MVVM) jest.mock("../useMyViewModel", () => ({ useMyViewModel: jest.fn() })); jest.spyOn(UseMyViewModel, "useMyViewModel").mockImplementation(() => ({ data: mockData, isLoading: false, })); // MSW override for error case server.use(http.get("/api/data", () => HttpResponse.json({ error: "Not found" }, { status: 404 })));
Before creating anything, check these locations:
| Location | What's there | | ------------------------------------------------- | --------------------------------------------------------- | | @ledgerhq/ledger-wallet-framework/mocks/account | genAccount(), genTokenAccount() | | @ledgerhq/live-common/currencies | getCryptoCurrencyById(), getTokenById() | | tests/handlers/ | MSW handlers (market, assets, countervalues, cryptoIcons) | | tests/handlers/fixtures/ | JSON fixtures for API responses | | tests/fixtures/ | mockedAccountList, expectedCurrencyList | | tests/mocks/ | Mock components, assets, countervalues |
bash# Run BEFORE writing new mocks rg "yourMockName|yourHelper" tests/ __tests__/ src/ libs/ --type ts
New reusable mocks go in tests/mocks/, new handlers in tests/handlers/, new fixtures in tests/handlers/fixtures/.
These cause flaky tests and mock cannibalization when running with parallel workers (--maxWorkers=50%).
Duplicate mocks — Don't jest.mock("module") in a test file when it's already mocked in jest-setup (apps/ledger-live-mobile/__tests__/jest-setup.js or apps/ledger-live-desktop/tests/jestSetup.js). Use jest.mocked(module.export).mockReturnValue(...) in beforeEach instead.
Hooks at describe load time — Never call renderHook(...) or access .result.current at the top level of a describe block. Move into beforeEach or inside each test — calling hooks outside of test callbacks crashes Jest parallel workers.
jest.restoreAllMocks() — Never use this; it restores global mocks from jest-setup and breaks other tests. Use jest.clearAllMocks() or mock.mockRestore() for specific spies only.
Wrong beforeEach order — Call jest.clearAllMocks() _before_ mockReturnValue() or other mock configuration — not after (calling clear after wipes the setup).
1. Search for existing mocks → 2. Write test → 3. Run test → 4. Fix if fail → 5. Next fileStart 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.
Other measured skills in the registry, with their headline benchmark lift.