Tested like it holds money

XChain processes token balances, DEX orders, and cross-chain swaps. Testing is a first-class engineering requirement, not an afterthought. Here's how the platform is verified before every release.

31,000+automated tests
12testing disciplines
13components covered
3build targets diffed for identical VM execution

Philosophy

Different bugs need different tests

Software that handles financial assets has no room for "it works on my machine." No single testing technique is sufficient on its own; together they form a defense-in-depth strategy that gives us confidence in every release.

Defense in depth

Unit tests catch logic errors; fuzz tests catch assumptions you didn't know you made; chaos tests catch failures you assumed couldn't happen. Each discipline catches a class the others can't.

Financial-grade correctness

A single uncaught edge case in the ledger could silently misattribute tokens; a race in reorg handling could corrupt a chain's state. Thorough testing before release is a necessity, not a nicety.

Deterministic & isolated

Tests are deterministic, isolated, and fast: no sleeps, no external network calls, no dependence on execution order. A green run is a trustworthy signal, not flaky noise.

The disciplines

Twelve kinds of test, twelve kinds of bug

Each discipline targets a category of failure the others would miss. Here's what each one is for.

Unit

Each function tested in isolation with dependencies mocked. Catches logic errors, wrong branches, off-by-one mistakes, bad return values, and missing null checks.

Integration

Real interfaces against a real MariaDB. Catches SQL errors, schema mismatches, broken JOINs, pagination bugs, and response-format regressions.

End-to-end

The full pipeline on a live regtest stack: encoder → decoder → indexer → explorer. Catches pipeline breaks and missing cross-service contracts.

Smoke

Fast "is it alive" checks that a service starts, connects, and answers. Catches broken imports, missing dependencies, and config errors in seconds.

Boundary

The exact edges of valid input: max/min values, length limits, precision thresholds. Catches off-by-one limits, integer overflow, and BigNumber precision loss.

Fuzz (property-based)

Thousands of random and adversarial inputs checked against invariants. Catches crashes on unexpected input, unicode handling, and math that breaks at extremes.

Security

Known attack classes thrown at the app (SQL injection, XSS, SSRF, path traversal, info leakage), verifying the defenses actually hold.

Performance & load

Baselines plus sustained concurrency. Catches slow queries, connection-pool exhaustion, memory leaks, and response-time regressions under load.

Chaos engineering

Injected failures: dropped database connections, latency, outages (via Toxiproxy). Verifies the system degrades gracefully and recovers cleanly.

Mutation

Small changes made to the source to check the suite catches them. Surfaces weak assertions and coverage gaps where code runs but isn't truly verified.

Determinism

Re-runs the same execution across machines, processes, and cache states and checks the result is byte-identical, the property consensus depends on. Catches non-determinism from ordering, floating point, time, or hidden state.

Regression

A curated cross-discipline subset in priority tiers, run on every change so fixed bugs stay fixed and critical paths keep working.

Coverage

Where the tests live

Every component that processes, stores, or serves token data ships its own full test suite. Click any type to jump straight to those tests in the repository.

The same data as a matrix: every component down the side, every test type across the top. Darker means more tests of that type; an empty cell means none. Columns are grouped into universal disciplines (expected across components, where an empty cell flags a possible gap) and specialized ones that are component-specific by design (consensus determinism, protocol actions, mutation-score tooling, where an empty cell is not a gap).

UniversalSpecialized · component-specific
UnitIntegrationE2ESmokeBoundaryFuzzSecurityPerformanceChaosRegressionMutationDeterminismActionsTotal
decoder777307242814274115931,191
indexer4,5242154321991235226441265,273
explorer1,93215549392261013715541482,765
encoder515791214210065716632421,241
utxo-tracker5476936920105272931123996
sdk2,45010428811365411328283,013
hub2,925917016263908742812183,883
vm677164641011557202576161841,615
sync1,2699765171645513639391,881
wallet4,66610916319744273061645,262
node1,11287575057952716121581,680
regtest-miner195802612189691352822147903
e2e-test615194391814453138801482561,568
All22,2041,4741,0934841,5387869882447141,2801268425631,271

Counts reflect where tests live. Some components also tag tests @regression / @tier N as a CI grep selector instead of a dedicated directory, most notably indexer, where the tag spans nearly the entire unit suite. Tagged tests are counted under their home discipline (usually Unit), so a cross-cutting tag never becomes a separate column. Where a discipline is covered only that way, the matrix marks the cell (hover for what it covers) rather than leaving it blank, so tag-homed coverage isn't misread as a gap. By the same rule, security- and performance-shaped assertions interwoven in feature, fuzz or chaos files (e.g. non-owner rejection or pipe-injection checks inside e2e-test's action suites) are credited to their home discipline; a Security or Performance column appears only where those tests have a dedicated home.

Browse the components →

How it runs

Nothing merges unverified

Every component's whole suite runs on every change, not a fast subset of it. Two further gates run on their own schedule, because the failures they catch do not arrive with a commit.

Every push, every pull request

All 13 components run their full suite on both. No tiering, no subset: the same tests that gate a release gate a typo fix.

Weekly: shipped dependencies

A vulnerability audit of production dependencies only, at high and critical severity. A CVE can be disclosed against code we already shipped, with no commit here to trigger a run.

Weekly: identical execution

The contract VM's fuzz corpus is replayed on three build targets (x86-64, ARM64, and musl libc) and their per-case consensus hashes diffed. One disagreement means two validators would fork.

Weekly: browser-engine age

The desktop wallet ships a browser engine, so it inherits that engine's vulnerabilities between releases. A weekly check reads the pinned Electron version and asks the registry how far behind the current security patch it is. It reports on a clock rather than blocking releases, and a check that cannot reach the registry goes red rather than quiet.

The full-stack test, which boots every service against a real coin node and moves real value on a private chain, is heavy enough to run on demand rather than on a clock. Inside the suites, regression cases also carry priority tags, so a developer can run the critical subset in seconds without waiting on the whole thing.

The toolbox

What it's built with

Standard, battle-tested tooling, shared across every component.

Mocha

The test runner across every component.

Sinon

Mocks, stubs, and spies for true isolation.

Chai

The assertion library (explorer, SDK).

Supertest

HTTP endpoint testing against real routes.

Nock

HTTP request mocking for the SDK and explorer.

fast-check

Property-based / fuzz testing (indexer, VM, SDK).

StrykerJS

The mutation-testing framework (explorer, encoder, VM).

Toxiproxy

Network fault injection for chaos tests.

Docker Compose

Test-environment orchestration for integration, chaos, and E2E.

Go deeper

The full reference covers every discipline, the per-component breakdowns, and how to run each suite yourself.

Full testing reference → ← Back home