Ledger Wallet Testing Skill
SkillDev toolsWrite 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.
No other account needed.
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
e2eskill
Golden Rules
toBeVisible()overtoBeInTheDocument()— Always.toBeInTheDocumentonly checks DOM presence; elements can be hidden. UsetoBeInTheDocumentonly when testing explicitly hidden elements.- Search before you create — Before writing any mock, fixture, or helper,
rgthe codebase. If it exists, import it. If your new mock is reusable (2+ files), put it in a shared location. - Mock external deps only — Never mock child components. Test integration.
- One behavior per test — Name:
it("should <behavior> when <condition>"). - Query priority:
getByRole>getByLabelText>getByText>getByTestId(last resort). - Feature flags via store, never mocked — Use
overriddenFeatureFlagsininitialState.settings. - 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
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)
| Platform | Render Import | MSW Server |
|---|---|---|
| Desktop | tests/testSetup | tests/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:
| 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 |
# 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