Skip to main content
A mapping bug usually surfaces as a fatal indexing error hours into a sync: the subgraph stalls, and the fix costs you a redeploy and a re-sync. Catching the same bug in a unit test takes seconds. Goldsky runs standard graph-node, so the standard subgraph testing toolchain works unchanged. Matchstick runs your mapping handlers against mocked events in a local WASM runtime: no chain, no deploy, and no Goldsky account needed to run tests. This guide walks through a complete setup: installing Matchstick, testing a Transfer handler, mocking eth_calls, and gating deploys on tests in CI.

What you’ll need

  1. A code-based subgraph project: a schema.graphql, subgraph.yaml, and mapping code in src/. If you’re starting from scratch, see deploying from source code.
  2. Node.js and the @graphprotocol/graph-cli package (Matchstick’s test runner is the graph test command).
Instant (no-code) subgraphs generate their mapping code from configuration, so there’s nothing to unit test. This guide applies to subgraphs with hand-written AssemblyScript mappings.

Install Matchstick

Add the matchstick-as assertion library as a dev dependency:
The test runner itself ships with graph-cli: the first graph test run downloads the Matchstick binary for your platform. If the binary doesn’t support your OS, you can run the tests in Docker instead with graph test -d.

The handler under test

We’ll test an ERC-20 Transfer handler that records an immutable transfer log and caches token metadata on first sight, following the patterns from the performance guide.
schema.graphql
src/mapping.ts
Note the rename: the event parameter is value, but the entity field is amount. Tests assert on entity fields, so this is exactly the kind of detail a test pins down.

Write the test

Tests live in a tests/ directory and end in .test.ts. A test file has three parts: a helper that builds a typed mock event, mocks for any contract calls the handler makes, and the tests themselves.
tests/token.test.ts
A few things to notice:
  • newMockEvent() returns a generic event; changetype<TransferEvent>() casts it to your generated event type so the handler accepts it.
  • clearStore() in beforeEach resets the mock store, so tests stay independent.
  • The expected entity id is computed from the mock event itself, the same way the handler computes it, with no hardcoded hashes.
  • assert.entityCount, assert.fieldEquals, and assert.notInStore cover most assertions. All field values are compared as strings.

Run the tests

Generate types first (the test file imports from ../generated), then run the suite:
To run a single test file, pass its name:

Mocking eth_calls

Any contract call your handler makes must be mocked, or the test fails when the call happens. createMockedFunction takes the contract address, the function name, and the full function signature:
Use .reverts() deliberately: every try_ call in your mappings has a revert branch, and untested revert branches are where subgraphs crash in production. If your subgraph uses data source templates, dataSourceMock lets you simulate the template’s context in tests.

Edge cases worth a test

These are the inputs that most often crash subgraphs in production. Cover them for every handler:
  • Zero-value transfers: emitted by many tokens, easy to divide by.
  • Self-transfers: from equals to; double-counting bugs live here.
  • Reverting metadata calls: a non-ERC-20 contract whose decimals() or symbol() reverts.
  • Max BigInt values: the full uint256 range, not what fits in an i32.
Beyond inputs, write tests that would catch the classic mapping bugs: a force-unwrapped Entity.load(id)! that panics when the entity is missing (use get-or-create instead), division without a zero check, an early return that skips .save() and leaves a later load() to panic, and a stale .save() that overwrites fields a helper function already updated.

Lint before you deploy

Static analysis catches those same mapping mistakes without writing a test for each one. The Subgraph Linter’s high-value checks map one-to-one onto the failures that become fatal indexing errors in production: Run the linter in CI alongside graph test so none of these reach a deploy.

Run tests in CI

Gate every deploy on a green build and test run:
Here’s the same gate as a GitHub Actions workflow. Tests run on every push and pull request; the deploy job only runs on main, and only after tests pass. Create an API key in your project settings and store it as a repository secret named GOLDSKY_TOKEN.
.github/workflows/subgraph.yml
Update the my-subgraph/1.0.0 name and version to match your project; see Deploy a subgraph for the deploy command’s options.