# How to Write Tests

This guide shows you how to write and run tests in the Ledger Wallet monorepo. It covers integration tests, component tests, API mocking, debugging, and common patterns.

> **Warning:** When you fix an issue, always provide a test to ensure the same error does not
> occur again.

## Testing requirements

| Scenario                                   | Requirement                                                                      |
| ------------------------------------------ | -------------------------------------------------------------------------------- |
| **New features** in `features/` or `mvvm/` | Must include unit and integration tests                                          |
| **Bug fixes** in new architecture          | Must include a regression test covering the use case                             |
| **New components** in legacy code          | Use new arch patterns to ease future migration                                   |
| **Major feature reworks** in legacy code   | Consider full migration to new architecture with tests (coordinate with Product) |

---

## How to write an integration test

For features with many components, integration tests provide better coverage than unit tests. Use the [custom renderer](./use-custom-renderer) to avoid manual provider setup.

```tsx
// __integrations__/searchACoin.integration.test.tsx
import * as React from "react";
import {
  screen,
  waitForElementToBeRemoved,
} from "@testing-library/react-native";
import { render } from "@tests/test-renderer";
import { MarketPages } from "./shared";
import { State } from "~/reducers/types";

describe("Market integration test", () => {
  it("Should search for a coin and navigate to detail page", async () => {
    const { user } = render(<MarketPages />, {
      overrideInitialState: (state: State) => ({
        ...state,
        settings: {
          ...state.settings,
          featureFlags: { llmMarketNewArch: { enabled: true } },
        },
      }),
    });

    expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen();
    expect(await screen.findByText("Ethereum (ETH)")).toBeOnTheScreen();

    const searchInput = await screen.findByTestId("search-box");
    await user.type(searchInput, "BTC");

    await waitForElementToBeRemoved(() => screen.queryByText("Ethereum (ETH)"));

    expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen();
    await user.press(screen.getByText("Bitcoin (BTC)"));

    expect(await screen.findByText("Price Statistics")).toBeOnTheScreen();
  });
});
```

---

## How to write a component test

For generic components used across the application:

```tsx
import Button from "./Button";

describe("Button component", () => {
  it("should be disabled", () => {
    render(
      <Button disabled type="submit" name="hello" className="mySuperClass">
        Hello
      </Button>
    );
    expect(screen.getByRole("button")).toBeDisabled();
  });

  it("should dispatch onClick event", () => {
    const mockFn = jest.fn();
    render(<Button onClick={mockFn}>Hello</Button>);

    userEvent.click(screen.getByRole("button"));

    expect(mockFn).toHaveBeenCalledTimes(1);
  });
});
```

---

## How to mock APIs with MSW

Use Mock Service Worker (MSW) to mock API calls. Place mock data in `__mocks__/api/*` and register handlers in `__tests__/handlers`.

### Set up shared handlers

```tsx
// __tests__/handlers/market.ts
import { http, HttpResponse } from "msw";
import marketsMock from "@mocks/api/market/markets.json";
import supportedVsCurrenciesMock from "@mocks/api/market/supportedVsCurrencies.json";

const handlers = [
  http.get(
    "https://proxycg.api.live.ledger.com/api/v3/coins/:coin/market_chart",
    ({ params }) => {
      return HttpResponse.json(
        marketsMock.find(({ id }) => id === params.coin)
      );
    }
  ),
  http.get(
    "https://proxycg.api.live.ledger.com/api/v3/simple/supported_vs_currencies",
    () => {
      return HttpResponse.json(supportedVsCurrenciesMock);
    }
  ),
];

export default handlers;
```

### Override handlers for specific tests

For edge cases where the default response is not appropriate:

```tsx
import { server, http, HttpResponse } from "@tests/server";

describe("Market page", () => {
  it("should display coins from API", async () => {
    server.use(
      http.get("https://proxycg.api.live.ledger.com/api/v3/coins/markets", () =>
        HttpResponse.json({
          data: [{ name: "Bitcoin (BTC)" }, { name: "Ethereum (ETH)" }],
        })
      )
    );

    const { user } = render(<MarketPages />);

    expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen();
    expect(await screen.findByText("Ethereum (ETH)")).toBeOnTheScreen();
  });
});
```

### Handle unhandled MSW requests

If your MSW handler is set up but the mock response is not used, the request may not have been intercepted because the test was stopped by Jest before the request was sent.

Make sure you use `waitFor` to assert the UI reflects the API response before the test ends:

```tsx
import { render, screen, waitFor } from "@tests/test-renderer";

render(<MyComponent />);

// Wait for the API response to be reflected in the UI
await waitFor(() => {
  expect(screen.getByText("Data from API")).toBeOnTheScreen();
});
```

> **Warning:** Always ensure your test waits for async operations to complete before assertions. This prevents race conditions where MSW is closed before the request is made.

---

## How to debug a failing test

### Use `screen.debug()`

```tsx
import { screen } from "@testing-library/react-native";

screen.debug();

// Debug a specific element
screen.debug(screen.getByText("test"));

// Increase the output limit (default is truncated)
screen.debug(undefined, 3000000);
```

**Resources:**

- [Debugging | Testing Library](https://testing-library.com/docs/dom-testing-library/api-debugging)
- [Testing Playground](https://testing-playground.com/) — Interactive tool for finding the right queries

### Print all available roles

To see all ARIA roles in your rendered component (useful for finding the right `ByRole` query):

```tsx
import { logRoles } from "@testing-library/dom";

const { container } = render(<MyComponent />);
logRoles(container);
```

---

## How to test element absence

Use `queryBy...` instead of `getBy...` when testing for non-existence.

> **Warning:** `getBy...` throws an error when the element doesn't exist. Use `queryBy...` instead, which returns `null` if no element is found.

```tsx
// ✅ Correct - queryBy returns null if not found
expect(screen.queryByRole("link", { name: /btc/i })).not.toBeInTheDocument();

// ❌ Wrong - getBy throws an error if not found
expect(screen.getByRole("link", { name: /btc/i })).not.toBeInTheDocument();
```

---

## How to force an initial state

You can customize the Redux state for your render call using `overrideInitialState`:

```tsx
const { user } = render(<MarketPages />, {
  overrideInitialState: (state: State) => ({
    ...state,
    settings: {
      ...state.settings,
      supportedCounterValues: SUPPORTED_CURRENCIES,
      featureFlags: { llmMarketNewArch: { enabled: true } },
    },
  }),
});
```

> **Note:** **Current Limitation:** Due to redux-actions, you need to spread the existing state when overriding specific parts. This prevents accidentally replacing the initial state of reducers.

---

## How to mock modules and functions

When a component calls a fetch or custom method that you don't need in your test, mock the module:

```tsx
jest.mock("./myModule", () => ({
  myFunction: jest.fn(() => "mocked value"),
}));
```

**Resources:**

- [ES6 Class Mocks - Jest](https://jestjs.io/docs/es6-class-mocks)

---

## How to run coverage

To generate a coverage report for Ledger Live Mobile:

```bash
pnpm mobile test:jest:coverage
```

The HTML report is available at:

```
apps/ledger-live-mobile/coverage/lcov-report/index.html
```

---

## See also

- [How to Use the Custom Renderer](./use-custom-renderer) — Render components with all providers pre-configured
- [Testing reference](../reference/testing) — Tools, coverage targets, renderer API, query priority
- [Testing Strategy](../explanation/testing-strategy) — Why we favor integration tests for UI and our testing principles
