Ledger Wallet Testing Skill

SkillDev tools

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*"

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Ledger Wallet Testing Skill skill

What this skill tells your AI

The instructions your AI receives, as published by ledgerhq/ledger-live in .agents/skills/testing/SKILL.md and read by ahel’s review.

Jest + MSW + React Testing Library (Desktop) / React Native Testing Library (Mobile) ❌ E2E tests → Use e2e skill


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. Mock external deps only — Never mock child components. Test integration.
  4. One behavior per test — Name: it("should <behavior> when <condition>").
  5. Query priority: getByRole > getByLabelText > getByText > getByTestId (last resort).
  6. Feature flags via store, never mocked — Use overriddenFeatureFlags in initialState.settings.
  7. Use existing factoriesgenAccount() from @ledgerhq/ledger-wallet-framework/mocks/account, getCryptoCurrencyById() from @ledgerhq/live-common/currencies. Never recreate account/currency data from scratch.

Quick Reference

pnpm 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)
PlatformRender ImportMSW Server
Desktoptests/testSetuptests/server.ts
Mobile@tests/test-renderer__tests__/server.ts

Test Template

import { 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());
  });
});

Testing Hooks

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:

render(<MyComponent />, {
  initialState: {
    settings: { theme: "dark" },
    accounts: { active: [mockAccount] },
  },
});

Mobile — function receiving default state:

render(<MyScreen />, {
  overrideInitialState: (state: State) => ({
    ...state,
    settings: { ...state.settings, blacklistedTokenIds: ["ethereum/erc20/usdt"] },
  }),
});

Feature flags (both platforms):

render(<MyComponent />, {
  initialState: {
    settings: {
      overriddenFeatureFlags: {
        myFlag: { enabled: true, params: { key: "value" } },
      },
    },
  },
});

Mocking Patterns

// 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 })));

Shared Resources — Search Here First

Before creating anything, check these locations:

LocationWhat's there
@ledgerhq/ledger-wallet-framework/mocks/accountgenAccount(), genTokenAccount()
@ledgerhq/live-common/currenciesgetCryptoCurrencyById(), 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
# 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/.


Mock Anti-Patterns

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).


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.

Signals

GitHub stars
618
Forks
490
Last commit
Sep 2026
Hacker News mentions
20
Advanced
Catalog kind
skill
Gateway key
testing-ledgerhq
Source
github.com/ledgerhq/ledger-live