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
- A code-based subgraph project: a
schema.graphql,subgraph.yaml, and mapping code insrc/. If you’re starting from scratch, see deploying from source code. - Node.js and the
@graphprotocol/graph-clipackage (Matchstick’s test runner is thegraph testcommand).
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 thematchstick-as assertion library as a dev dependency:
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-20Transfer 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
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 atests/ 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
newMockEvent()returns a generic event;changetype<TransferEvent>()casts it to your generated event type so the handler accepts it.clearStore()inbeforeEachresets 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, andassert.notInStorecover 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:
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:
.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:
fromequalsto; double-counting bugs live here. - Reverting metadata calls: a non-ERC-20 contract whose
decimals()orsymbol()reverts. - Max
BigIntvalues: the fulluint256range, not what fits in ani32.
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: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
my-subgraph/1.0.0 name and version to match your project; see Deploy a subgraph for the deploy command’s options.