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.
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).
| Universal | Specialized · component-specific | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Unit | Integration | E2E | Smoke | Boundary | Fuzz | Security | Performance | Chaos | Regression | Mutation | Determinism | Actions | Total | |
| decoder | 777 | 30 | 72 | 42 | 81 | 42 | 74 | 11 | 59 | 3 | 1,191 | |||
| indexer | 4,524 | 215 | 43 | 21 | 99 | 123 | 52 | 26 | 44 | ⌗ | 126 | 5,273 | ||
| explorer | 1,932 | 155 | 49 | 39 | 226 | 10 | 137 | 15 | 54 | 148 | 2,765 | |||
| encoder | 515 | 79 | 121 | 42 | 100 | 6 | 57 | 16 | 63 | 242 | 1,241 | |||
| utxo-tracker | 547 | 69 | 36 | 9 | 20 | 105 | 27 | 29 | 31 | 123 | 996 | |||
| sdk | 2,450 | 104 | 288 | 11 | 36 | 54 | 11 | 3 | 28 | 28 | 3,013 | |||
| hub | 2,925 | 91 | 70 | 16 | 263 | 90 | 87 | 42 | 81 | 218 | 3,883 | |||
| vm | 677 | 164 | 64 | 10 | 115 | 57 | 202 | 5 | 76 | 161 | 84 | 1,615 | ||
| sync | 1,269 | 97 | 65 | 17 | 164 | 55 | 136 | 39 | 39 | ⌗ | 1,881 | |||
| wallet | 4,666 | 109 | 163 | 197 | 44 | 27 | 30 | 6 | 16 | 4 | 5,262 | |||
| node | 1,112 | 87 | 57 | 50 | 57 | 95 | 27 | 16 | 121 | 58 | 1,680 | |||
| regtest-miner | 195 | 80 | 26 | 12 | 189 | 69 | 135 | 28 | 22 | 147 | 903 | |||
| e2e-test | 615 | 194 | 39 | 18 | 144 | 53 | 13 | 8 | 80 | 148 | 256 | 1,568 | ||
| All | 22,204 | 1,474 | 1,093 | 484 | 1,538 | 786 | 988 | 244 | 714 | 1,280 | 126 | 84 | 256 | 31,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.
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.