> For the complete documentation index, see [llms.txt](https://docs.envio.dev/llms.txt).

## Introduction

Envio ships with a built-in testing library that doubles as a development loop. `createTestIndexer()` runs your real handlers in-process, so you can iterate on logic and validate behavior without deploying anywhere. It's designed for:

- **TDD**: Write a failing test, implement the handler, capture the snapshot, commit
- **Unit tests**: Feed synthetic events directly into handlers to exercise edge cases in isolation
- **E2E tests against real blockchain data**: Pin a block range or let the indexer auto-detect the first block with events, and run your full handler pipeline end-to-end
- **Regression-proof assertions**: Inspect entities and per-block change sets, then lock in expected output with `toMatchInlineSnapshot`

The library integrates well with [Vitest](https://vitest.dev/) (recommended) and any other JavaScript-based testing framework.

## Getting Started

The simplest way to start is auto-exit mode — no block ranges, no mock events. The indexer automatically finds the first block with events and processes it.

```typescript
import { describe, it } from "vitest";
import { createTestIndexer } from "envio";

describe("Indexer Testing", () => {
  it("Should process first two blocks with events", async (t) => {
    const indexer = createTestIndexer();

    t.expect(
      await indexer.process({ chains: { 1: {} } }),
      "Should find the first block with an event on chain 1 and process it."
    ).toMatchInlineSnapshot(``);

    t.expect(
      await indexer.process({ chains: { 1: {} } }),
      "Should find the second block with an event on chain 1 and process it."
    ).toMatchInlineSnapshot(``);
  });
});
```

Run `pnpm test` — Vitest auto-fills the snapshots on first run. Review and commit them.

---

## Process API

`indexer.process({ chains })` is the single entry point for driving the indexer. The shape of each chain entry determines the mode.

### Auto-exit (recommended for getting started)

Processes the first block with matching events for each chain, then exits. Each subsequent call continues from where the previous one stopped.

```typescript
const result = await indexer.process({
  chains: {
    1: {},           // auto-detect first block with events on chain 1
    8453: {},        // same for chain 8453
  },
});
```

### Explicit block range

Process a specific block range. Use when you need deterministic, pinned snapshots.

```typescript
const result = await indexer.process({
  chains: {
    1: { startBlock: 10_000_000, endBlock: 10_000_100 },
  },
});
```

### Simulate (mock events)

Feed synthetic events without hitting the network. Best for unit-testing handler logic.

```typescript
await indexer.process({
  chains: {
    1: {
      simulate: [
        {
          contract: "ERC20",
          event: "Transfer",
          params: { from: addr1, to: addr2, value: 100n },
        },
      ],
    },
  },
});
```

You can pass multiple events in a single `simulate` array — they will be processed in order, just like in production.

You can optionally specify detailed event metadata per simulated event using the same `block` / `transaction` / `srcAddress` / `logIndex` shape that real events expose. See [field_selection](configuration-file#field-selection) for the full list of overridable fields.

### result.changes

`result.changes` is an array of per-block change objects. Each entry has `block`, `chainId`, `eventsProcessed`, plus entity names as keys with `sets` arrays of created/updated entities. Dynamic contract registrations appear under `addresses.sets`.

---

## Entity State API

Preset state before processing and read entities after.

```typescript
// Preset state before processing
indexer.EntityName.set({ id: "...", field: value });

// Read state after processing
await indexer.EntityName.get("id");        // returns entity | undefined
await indexer.EntityName.getOrThrow("id"); // throws if not found
await indexer.EntityName.getAll();         // returns all entities of this type
```

---

## Assertions

The testing library works with any JavaScript assertion library. The examples below use Vitest's built-in `expect`.

```typescript
// Snapshot (recommended — captures full output, auto-filled on first run)
t.expect(result.changes).toMatchInlineSnapshot(`...`);

// Entity assertions
const pool = await indexer.Pool.getOrThrow(poolId);
t.expect(pool).toEqual({ id: poolId, token0_id: "0xabc..." });

// Count
t.expect(result.changes[0]?.Pair?.sets).toHaveLength(1);

// Contract addresses (after dynamic registration)
t.expect(indexer.chains[1].MyContract.addresses).toContain("0x1234...");
```

---

## TDD Workflow

1. **Write a failing test** with expected entity output
2. **Implement the handler** until the test passes
3. **Capture the snapshot** — run `pnpm test` to fill `toMatchInlineSnapshot`
4. **Review and commit** the snapshot for regression testing

:::warning
Do not add tests which simply restate the implementation. These provide zero confidence.
:::

### Running Tests

```bash
pnpm test              # Run all tests
pnpm test -- -u        # Update snapshots
```

