ByAUJay
Building a Testing Strategy for Blockchain: Unit, Integration, and Fuzzing
A practical blueprint for decision‑makers to stand up a modern blockchain testing program in weeks, not months—covering what to test, which tools to use on EVM and permissioned stacks, and how to wire unit, integration, and fuzz/invariant testing into CI with measurable gates.
In 2025, protocol upgrades (Prague/Pectra/Osaka), new compiler behaviors, and maturing fuzzing frameworks make yesterday’s test plans incomplete; this guide distills the latest practices we deploy at 7Block Labs to keep teams ahead of the curve. (soliditylang.org)
Why blockchain testing is different in 2025
- Protocol churn impacts assumptions: Solidity 0.8.30 changed the compiler’s default EVM to “prague” (aligned with Pectra), and 0.8.31 moved to “osaka”—affecting gas accounting and opcode availability (for example, CLZ via EIP‑7939). Test against the target EVM version explicitly. (soliditylang.org)
- Cost models evolve: Pectra’s EIP‑7623 increased calldata costs; regression tests should include calldata‑heavy paths and budget alerts. (soliditylang.org)
- Local devnets are smarter: Hardhat and Anvil support deterministic mainnet forking; pinning a block stabilizes tests and can speed repeated runs by up to 20×. (hardhat.org)
- Invariants > examples: Property/invariant testing and coverage‑guided fuzzing now catch classes of bugs traditional unit tests miss; plan for both. (learnblockchain.cn)
The test strategy blueprint
Think in layers. For each component (smart contracts/chaincode, off‑chain services, client gateways), define:
- Unit tests: fast, isolated, deterministic.
- Integration tests: realistic networks (forked or test networks), with external dependencies and permissions.
- Fuzzing and invariants: property‑based, stateful, and adversarial simulations.
Below are concrete, stack‑specific playbooks.
Unit testing that actually catches blockchain bugs
EVM (Solidity) with Foundry
Foundry executes tests in Solidity, so you can create exhaustive test harnesses with almost zero context‑switching.
- Property‑based fuzz tests: any test with parameters is fuzzed; use type narrowing and assumptions to tame the input space. (learnblockchain.cn)
- Invariant tests: define always‑true conditions (e.g., conservation of value). Configure global/per‑test runs via
or in‑line pragmas. (learnblockchain.cn)foundry.toml - Gas budgets: enforce gas deltas with
andforge snapshot
. Include section‑level snapshots to gate hot paths. (learnblockchain.cn)FORGE_SNAPSHOT_CHECK=true
Example (fuzz + invariant + gas gate):
// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import "forge-std/Test.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract Token is ERC20("T","T"){ function mint(address to,uint256 a) external { _mint(to,a);} } contract TokenTest is Test { /// forge-config: default.fuzz.runs = 512 Token t; function setUp() public { t = new Token(); } // Property: totalSupply equals sum of balances across holders we track. function testFuzz_MintConservation(address who, uint96 amt) public { vm.assume(who != address(0)); uint256 pre = t.totalSupply(); t.mint(who, amt); // section-level gas snapshot for the mint path vm.startSnapshotGas("mint"); t.mint(who, 1); vm.stopSnapshotGas(); assertEq(t.totalSupply(), pre + amt + 1); assertEq(t.balanceOf(who), amt + 1); } // Invariant: balances never exceed totalSupply (holds across sequences) function invariant_BalancesNotExceedSupply() public { assertGe(t.totalSupply(), 0); } }
- Pin your EVM version in
to match deployment (e.g.,foundry.toml
orevm_version = "prague"
), otherwise compiler defaults may drift under you. (soliditylang.org)"osaka"
Static analysis in unit loops:
- Run Slither in CI to catch upgradeability hazards, dead code, and common vulns at linter‑like speed; it integrates directly with Foundry/Hardhat projects. (github.com)
Hyperledger Fabric chaincode
- The official Fabric test network (
) spins up two orgs + one orderer via Docker Compose; use it for chaincode unit/integration exercises, not as a production topology template. (hyperledger-fabric.readthedocs.io)fabric-samples/test-network - Scripted flows (
) allow rapid install/instantiate of chaincode for test runs. Parameterize channel names and CA usage to validate identity paths. (hyperledger-fabric.readthedocs.io)./network.sh deployCC
Cosmos SDK modules (Golang)
- Treat module keepers as units with mocks; then step up to SDK “integration tests” under
. The Simulator adds randomized messages to fuzz state transitions pre‑mainnet. (docs.cosmos.network)/tests/integrations
Integration tests that reflect real networks
Mainnet‑fork tests (EVM)
- Hardhat: start a node forked from mainnet or any EVM chain; pin a block for speed and determinism; impersonate accounts and manipulate timestamps/balances with Network Helpers. (v2.hardhat.org)
- Anvil (Foundry):
gives a blazing‑fast forked devnet with deterministic accounts; ideal for Solidity‑native tests. (getfoundry.sh)anvil --fork-url <RPC>
Example (Hardhat config):
import { HardhatUserConfig } from "hardhat/config"; const config: HardhatUserConfig = { networks: { hardhat: { forking: { url: process.env.MAINNET_RPC!, blockNumber: 20400000 // pin for deterministic caches } } }, // Align local EVM to target hardfork hardhat: { hardfork: "prague" } }; export default config;
- If you build on Hedera’s EVM, use the Hardhat forking plugin to emulate HTS system contract reads in fork tests. (github.com)
Permissioned networks
- Fabric: bring up the test network, then run client flows (FireFly or SDK) against org connection profiles; verify cert material mapping and channel ACLs. (hyperledger-fabric.readthedocs.io)
- Cross‑DLT: for interop proofs‑of‑concept, the Hyperledger Weaver test setups provide dual networks + contracts to validate data sharing/asset exchange. (hyperledger-labs.github.io)
Cross‑client/spec conformance (EVM teams)
- Use Ethereum Execution Spec Tests (EEST/EELS). In late 2025, the “Weld” merged tests and the Python spec into one repo, simplifying generation of fixtures and client‑agnostic test vectors—useful for validation of custom precompiles or low‑level libraries. (steel.ethereum.foundation)
Fuzzing and invariant testing: what to run and where
Foundry fuzzing and invariants (EVM)
- Fuzz any parameterized test; control runs globally (
) or per test via in‑line config comments. Pair with deterministic seeds in CI. (learnblockchain.cn)FOUNDRY_FUZZ_RUNS - Invariant testing supports multiple styles (direct checks, differential checks, and “targeted” handlers for stateful sequences). Use it as a pre‑merge gate on DeFi protocols. (learnblockchain.cn)
- Differential fuzzing: compare Solidity output to a reference implementation (TS/Rust) using
to catch semantic mismatches in crypto/maths libs. (foundry-book.zksync.io)vm.ffi()
Echidna (coverage‑guided ABI fuzzer)
- Great for black‑box ABI sequences with user‑defined invariants; integrates with Slither for pre‑analysis and produces minimized counterexamples for triage. We use it to complement Foundry where grammar‑based campaigns shine. (github.com)
Certora & symbolic tools
- Certora’s Foundry integration (alpha) can formally verify your Foundry fuzz tests—exhaustively exploring inputs instead of sampling. Ideal for turning high‑value fuzz specs into proofs over time. (docs.certora.com)
- Halmos provides symbolic testing with a Foundry frontend and recent support for stateful invariants; use it to transform existing tests into solver‑driven checks. (github.com)
Client and off‑chain fuzzing
- Go clients/middleware: use Go’s built‑in fuzzing (since 1.18) to generate invalid RPCs, malformed blocks/receipts, and ABI edge cases; integrate with CI to catch panics and resource leaks. (go.dev)
- OSS‑Fuzz: if your project is open source (clients, SDKs, precompiles), wire sanitizers and continuous fuzzing with libFuzzer/AFL++/Honggfuzz at scale. (github.com)
Non‑EVM smart contracts
- Cosmos SDK: run the Simulation framework to fuzz message sequences and module parameters; it’s the canonical way to surface halt‑inducing state transitions pre‑launch. (docs.cosmos.network)
- Stellar Soroban: use
with nightly Rust to fuzz contract interfaces; property tests live alongside conventional tests. (developers.stellar.org)cargo-fuzz
Measuring what matters (and failing the build on regressions)
Adopt objective gates that match risk:
- Security lint: Slither must pass with no “High” detectors and a clean upgradeability check. (github.com)
- Gas budget:
must pass; add section‑level snapshot gates for hot paths. (learnblockchain.cn)forge snapshot --check --tolerance 1 - Fuzzing: minimum runs per suite (e.g., ≥2k locally, ≥10k in nightly) and “no failures after shrinking.” Configure with in‑line
and CI profiles. (learnblockchain.cn)forge-config - Invariants: zero violations after ≥1 min per suite or ≥N sequences (set per protocol risk). (learnblockchain.cn)
- Fork tests: pinned‑block fork CI must pass; if unpinned, the job is allowed to be flaky only on nightly (never on PR). Hardhat pinning also accelerates caching drastically. (hardhat.org)
- Spec conformance: for client/low‑level libs, run a smoke subset of EELS tests in CI; run full suites nightly. (steel.ethereum.foundation)
A reference CI pipeline you can copy
Minimal GitHub Actions fragments to establish gates quickly:
# .github/workflows/ci.yml name: smart-contract-ci on: [pull_request, push] jobs: solidity-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: foundry-rs/foundry-toolchain@v1 with: { version: nightly } - name: Unit + fuzz (quick) run: | FOUNDRY_FUZZ_RUNS=512 forge test --gas-report - name: Gas snapshot gate env: FORGE_SNAPSHOT_CHECK: "true" run: forge snapshot --check --tolerance 1 - name: Slither run: | pipx install slither-analyzer slither . hardhat-fork: runs-on: ubuntu-latest env: MAINNET_RPC: ${{ secrets.MAINNET_RPC }} steps: - uses: actions/checkout@v4 - run: npx hardhat test --network hardhat
- For Fabric/Weaver repos, add a job that runs
and your chaincode test harness, then./network.sh up createChannel
to reset. (hyperledger-fabric.readthedocs.io)./network.sh down
Tool choices by stack (what we deploy most)
- EVM smart contracts: Foundry (unit/fuzz/invariants, gas reports), Hardhat (forked integration, JS helpers), Slither (static), Echidna (grammar‑based fuzz), Certora/ Halmos (symbolic), EELS tests (conformance), Anvil for forked devnets. (learnblockchain.cn)
- Fabric: Fabric test network + FireFly/Weaver for integration scenarios. (hyperledger-fabric.readthedocs.io)
- Cosmos SDK: Go tests + Integration tests + Simulator. (docs.cosmos.network)
- Soroban/Rust: cargo‑fuzz (libFuzzer) and property tests. (developers.stellar.org)
- Clients/SDKs (Go/Rust/C++): Go fuzzing, OSS‑Fuzz for continuous sanitization. (go.dev)
Emerging practices we recommend adopting now
- Align tests to the deployment hardfork: set the EVM (“prague” or “osaka”) in compiler/tooling to avoid subtle opcode/gas shifts; update when bumping Solidity (0.8.30→0.8.31+). (soliditylang.org)
- Treat calldata as a cost surface: after EIP‑7623, create regression tests that submit large calldata (e.g., batched mints, proofs) and assert against gas ceilings to protect UX. (soliditylang.org)
- Make invariants your primary pre‑merge gate: we routinely uncover class‑breakers (fee drifts, liquidity leaks) only visible under stateful fuzz sequences. (learnblockchain.cn)
- Borrow from spec tests: if you ship low‑level primitives (RLP, Merkle, arithmetic), run EELS fixtures as differential tests alongside your own suite. (steel.ethereum.foundation)
Research to watch: LLM‑guided fuzzers and automated invariant synthesis are moving from papers to practice—directionally useful for prioritizing fuzz campaigns and bootstrapping properties, but still need human review before gating prod. (arxiv.org)
Pitfalls we still see—and fixes
- “It passed unit tests on a dev chain” is not enough: without mainnet‑fork tests and impersonation you miss real allowance/permit states, fee on transfer, and proxy behaviors. Use fork + pin. (hardhat.org)
- Fuzzing without assumptions: unconstrained domains produce false negatives (e.g., sending >2^96 wei in Foundry defaults). Narrow types and add
. (learnblockchain.cn)vm.assume - Gas regressions slipping in: lock
in CI with a 1% tolerance; add section snapshots for critical flows (e.g., swap, settle). (learnblockchain.cn).gas-snapshot - Fabric misuse of the test network: it’s for testing only; don’t mirror its single‑node ordering/TLS shortcuts in prod rollouts. (hyperledger-fabric.readthedocs.io)
Bringing it together: a 30‑day rollout plan
- Week 1: adopt Foundry or Hardhat+Foundry for EVM; set compiler EVM to your deployment hardfork; add Slither and quick fuzz runs to CI; stand up Fabric/Cosmos local networks as needed. (soliditylang.org)
- Week 2: write 5–10 invariants that reflect key financial or access properties; enable
gates; add a pinned‑block fork suite for top integration paths. (learnblockchain.cn)forge snapshot - Week 3: integrate Echidna for ABI‑level campaigns and start nightly long‑run fuzz; wire OSS‑Fuzz or Go fuzzing for clients/SDKs. (github.com)
- Week 4: run EELS/EEST conformance where relevant; evaluate Certora/Halmos on the most valuable invariants to convert fuzz specs into formal guarantees. (steel.ethereum.foundation)
Final word
Testing blockchain software in 2025 is about aligning to fast‑moving protocol targets, then proving properties under realistic, adversarial conditions. If you adopt the playbooks above—unit rigor, forked integration, and property‑driven fuzz/invariants with hard CI gates—you’ll ship faster with fewer late‑stage surprises.
7Block Labs has standardized these practices across EVM and permissioned stacks; if you’d like us to tailor the blueprint to your architecture and risk profile, we’re here to help.
References
- Solidity 0.8.30/0.8.31 release notes (hardfork defaults, EIPs). (soliditylang.org)
- Hardhat Network fork docs and performance guidance; Helpers for impersonation/snapshots. (hardhat.org)
- Foundry fuzzing/invariant and in‑line config docs; gas snapshot tooling. (learnblockchain.cn)
- Anvil overview (forking). (getfoundry.sh)
- Echidna (ABI fuzzing). (github.com)
- Slither static analysis. (github.com)
- Fabric test network; FireFly connection profiles. (hyperledger-fabric.readthedocs.io)
- Cosmos SDK testing/simulator. (docs.cosmos.network)
- Go fuzzing; OSS‑Fuzz. (go.dev)
- EELS/EEST “The Weld” merge (spec tests). (steel.ethereum.foundation)
Like what you're reading? Let's build together.
Get a free 30‑minute consultation with our engineering team.

