Skip to main content

Fork Testing with Cadence

This tutorial teaches you how to run your Cadence tests against a snapshot of Flow mainnet using flow test --fork. You'll learn how to test your contracts against real deployed contracts and production data without needing to deploy anything to a live network or bootstrap test accounts.

Fork testing bridges the gap between isolated local unit tests and testnet deployments. It enables you to validate your contracts work correctly with real on-chain state, test integrations with deployed contracts, and debug issues using historical blockchain data—all in a safe, local environment.

What You'll Learn

After completing this tutorial, you'll be able to:

  • Run Cadence tests against forked networks using flow test --fork
  • Test contracts that depend on real mainnet contracts without manual setup
  • Use account impersonation to execute transactions as any mainnet account
  • Read from production blockchain state in your test suite
  • Pin tests to specific block heights for historical debugging
  • Integrate fork testing into your development workflow

What You'll Build

You'll create a complete fork testing setup that demonstrates:

  • Reading from the live FlowToken contract on mainnet
  • Deploying your own contract that interacts with mainnet contracts
  • Testing custom logic against real account balances and state
  • Executing transactions using impersonated mainnet accounts
  • A reusable pattern for integration testing your Flow applications

Time Commitment: Approximately 30 minutes

Reproducibility first

Pin a specific block height when you need reproducible results:


_10
flow test --fork mainnet --fork-height <BLOCK_HEIGHT>

Document the pin heights you rely on (for example in CI variables or a simple file in the repo) and update them via a dedicated freshness PR. For best results, keep a per‑spork stable pin and also run a "latest" freshness job.

Prerequisites

Flow CLI

This tutorial requires Flow CLI v1.8.0 or later installed. If you haven't installed it yet and have homebrew installed, run:


_10
brew install flow-cli

For other operating systems, refer to the installation guide.

Basic Cadence Testing Knowledge

You should be familiar with writing basic Cadence tests. If you're new to Cadence testing, start with Testing Smart Contracts first.

Network Access

You'll need network access to Flow's public access nodes. The tutorial uses these endpoints, which are freely available:

  • Mainnet: access.mainnet.nodes.onflow.org:9000
  • Testnet: access.devnet.nodes.onflow.org:9000
info

This tutorial covers flow test --fork (running tests against forked network state), which is different from flow emulator --fork (starting the emulator in fork mode for manual interaction).

Create Your Project

Navigate to your development directory and create a new Flow project:


_10
mkdir fork-testing-demo
_10
cd fork-testing-demo
_10
flow init --yes

The --yes flag accepts defaults non-interactively. flow init is interactive by default and can scaffold various templates.

Alternatively, create the directory and initialize in one command:


_10
flow init fork-testing-demo --yes
_10
cd fork-testing-demo

Install Dependencies

Use the Dependency Manager to install the FlowToken and FungibleToken contracts:


_10
flow dependencies install FlowToken FungibleToken

This downloads the contracts and their dependencies into the imports/ folder and updates your flow.json with the correct addresses and aliases across all networks (mainnet, testnet, emulator).

Your flow.json will now include an entry like:


_12
{
_12
"dependencies": {
_12
"FlowToken": {
_12
"source": "mainnet://1654653399040a61.FlowToken",
_12
"aliases": {
_12
"emulator": "0ae53cb6e3f42a79",
_12
"mainnet": "1654653399040a61",
_12
"testnet": "7e60df042a9c0868"
_12
}
_12
}
_12
}
_12
}

Your flow.json should now have the mainnet and testnet networks configured from flow init. In fork mode, contract imports automatically resolve to the correct network addresses.

Test Reading Live State

The test directories are already created by flow init. If needed, create the scripts directory:


_10
mkdir -p cadence/scripts

First, create a script to read FlowToken supply. Create cadence/scripts/GetFlowTokenSupply.cdc:

cadence/scripts/GetFlowTokenSupply.cdc

_10
import "FlowToken"
_10
_10
access(all) fun main(): UFix64 {
_10
return FlowToken.totalSupply
_10
}

Now create the test file cadence/tests/flow_token_test.cdc:

cadence/tests/flow_token_test.cdc

_13
import Test
_13
_13
access(all) fun testFlowTokenSupplyIsPositive() {
_13
let scriptResult = Test.executeScript(
_13
Test.readFile("../scripts/GetFlowTokenSupply.cdc"),
_13
[]
_13
)
_13
_13
Test.expect(scriptResult, Test.beSucceeded())
_13
_13
let supply = scriptResult.returnValue! as! UFix64
_13
Test.assert(supply > 0.0, message: "FlowToken supply should be positive")
_13
}

Notes:

  • Use Test.executeScript() to read contract state
  • The script imports FlowToken by name - the dependency manager handles address resolution
  • In fork mode, this automatically uses the mainnet FlowToken contract
  • Extract the return value with proper type casting and assert on it
  • File paths in Test.readFile() are relative to the test file location (use ../scripts/ from cadence/tests/)

Quick verify

Run just this test file against a fork to confirm your setup works:


_10
flow test cadence/tests/flow_token_test.cdc --fork mainnet

Target testnet instead:


_10
flow test cadence/tests/flow_token_test.cdc --fork testnet

You should see the test PASS. If not, verify your network host in flow.json and that dependencies are installed.

Deploy and Test Your Contract

Now you'll create a contract that depends on FlowToken and test it against the forked mainnet state—no need to bootstrap tokens or set up test accounts.

Generate a Key Pair

Generate a key pair that will be used for your test contract's mainnet alias:


_10
flow keys generate

This will output a public/private key pair. Save the private key to a file:


_10
echo "YOUR_PRIVATE_KEY_HERE" > mainnet-test.pkey

Note the public key - you'll need it to derive an account address. For fork testing, any valid Flow address format works. You can use this command to generate a test address from your key:


_10
flow accounts derive-address mainnet-test.pkey

Or simply use a placeholder mainnet-format address like f8d6e0586b0a20c7 for testing purposes.

Create a Contract that Uses FlowToken

Generate a new contract:


_10
flow generate contract TokenChecker

This creates cadence/contracts/TokenChecker.cdc and adds it to flow.json. Now update the contract with your logic:

cadence/contracts/TokenChecker.cdc

_18
import "FlowToken"
_18
_18
access(all) contract TokenChecker {
_18
_18
access(all) fun checkBalance(address: Address): UFix64 {
_18
let account = getAccount(address)
_18
_18
let vaultRef = account.capabilities
_18
.borrow<&FlowToken.Vault>(/public/flowTokenBalance)
_18
?? panic("Could not borrow FlowToken Vault reference")
_18
_18
return vaultRef.balance
_18
}
_18
_18
access(all) fun hasMinimumBalance(address: Address, minimum: UFix64): Bool {
_18
return self.checkBalance(address: address) >= minimum
_18
}
_18
}

Configure Contract in flow.json

Add the TokenChecker contract configuration to flow.json. The contract needs a mainnet alias so that imports can resolve properly during fork testing.

Update your flow.json to include the contract with aliases, using the address you generated in the previous step:


_20
{
_20
"contracts": {
_20
"TokenChecker": {
_20
"source": "cadence/contracts/TokenChecker.cdc",
_20
"aliases": {
_20
"testing": "0000000000000008",
_20
"mainnet": "<from_previous_step>"
_20
}
_20
}
_20
},
_20
"accounts": {
_20
"mainnet-test": {
_20
"address": "<from_previous_step>",
_20
"key": {
_20
"type": "file",
_20
"location": "mainnet-test.pkey"
_20
}
_20
}
_20
}
_20
}

The Test.deployContract function will automatically deploy your contract to the testing environment during test execution.

Create Scripts for Testing

Create cadence/scripts/CheckBalance.cdc:

cadence/scripts/CheckBalance.cdc

_10
import "TokenChecker"
_10
_10
access(all) fun main(addr: Address): UFix64 {
_10
return TokenChecker.checkBalance(address: addr)
_10
}

Create cadence/scripts/HasMinimumBalance.cdc:

cadence/scripts/HasMinimumBalance.cdc

_10
import "TokenChecker"
_10
_10
access(all) fun main(addr: Address, min: UFix64): Bool {
_10
return TokenChecker.hasMinimumBalance(address: addr, minimum: min)
_10
}

Test Your Contract with Forked State

Create cadence/tests/token_checker_test.cdc:

cadence/tests/token_checker_test.cdc

_37
import Test
_37
_37
access(all) fun setup() {
_37
// Deploy TokenChecker to the test account
_37
let err = Test.deployContract(
_37
name: "TokenChecker",
_37
path: "../contracts/TokenChecker.cdc",
_37
arguments: []
_37
)
_37
Test.expect(err, Test.beNil())
_37
}
_37
_37
access(all) fun testCheckBalanceOnRealAccount() {
_37
// Test against a real mainnet account (Flow service account)
_37
let scriptResult = Test.executeScript(
_37
Test.readFile("../scripts/CheckBalance.cdc"),
_37
[Address(0x1654653399040a61)] // Flow service account on mainnet
_37
)
_37
_37
Test.expect(scriptResult, Test.beSucceeded())
_37
_37
let balance = scriptResult.returnValue! as! UFix64
_37
// The Flow service account should have a balance
_37
Test.assert(balance > 0.0, message: "Service account should have FLOW tokens")
_37
}
_37
_37
access(all) fun testHasMinimumBalance() {
_37
let scriptResult = Test.executeScript(
_37
Test.readFile("../scripts/HasMinimumBalance.cdc"),
_37
[Address(0x1654653399040a61), 1.0]
_37
)
_37
_37
Test.expect(scriptResult, Test.beSucceeded())
_37
_37
let hasMinimum = scriptResult.returnValue! as! Bool
_37
Test.assert(hasMinimum == true, message: "Service account should have at least 1 FLOW")
_37
}

What's Happening Here

  1. Your contract uses FlowToken: TokenChecker imports and interacts with the real FlowToken contract
  2. No bootstrapping needed: When you run with --fork, real mainnet accounts (like 0x1654653399040a61, the Flow service account) already have balances
  3. Test against real state: You can query actual accounts and verify your contract logic works with production data
  4. Local deployment: Your TokenChecker contract is deployed locally to the test environment, but it reads from forked mainnet state

Execute Transactions with Account Impersonation

Fork testing includes built-in account impersonation—you can execute transactions as any mainnet account without needing private keys. This lets you test interactions with real accounts and their existing state.

Create Transactions

Create the transactions directory:


_10
mkdir -p cadence/transactions

First, create a transaction to set up a FlowToken vault. Create cadence/transactions/SetupFlowTokenVault.cdc:

cadence/transactions/SetupFlowTokenVault.cdc

_13
import "FungibleToken"
_13
import "FlowToken"
_13
_13
transaction {
_13
prepare(signer: auth(Storage, Capabilities) &Account) {
_13
if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil {
_13
signer.storage.save(<-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()), to: /storage/flowTokenVault)
_13
let cap = signer.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault)
_13
signer.capabilities.publish(cap, at: /public/flowTokenReceiver)
_13
signer.capabilities.publish(cap, at: /public/flowTokenBalance)
_13
}
_13
}
_13
}

Now create the transfer transaction. Create cadence/transactions/TransferTokens.cdc:

cadence/transactions/TransferTokens.cdc

_23
import "FungibleToken"
_23
import "FlowToken"
_23
_23
transaction(amount: UFix64, to: Address) {
_23
let sentVault: @{FungibleToken.Vault}
_23
_23
prepare(signer: auth(Storage) &Account) {
_23
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
_23
from: /storage/flowTokenVault
_23
) ?? panic("Could not borrow reference to the owner's Vault")
_23
_23
self.sentVault <- vaultRef.withdraw(amount: amount)
_23
}
_23
_23
execute {
_23
let recipient = getAccount(to)
_23
let receiverRef = recipient.capabilities
_23
.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
_23
?? panic("Could not borrow receiver reference")
_23
_23
receiverRef.deposit(from: <-self.sentVault)
_23
}
_23
}

Test Transaction Execution with Impersonation

Add this test function to the existing cadence/tests/token_checker_test.cdc file:


_61
access(all) fun testTransactionAsMainnetAccount() {
_61
// Impersonate the Flow service account (or any mainnet account)
_61
// No private keys needed - fork testing has built-in impersonation
_61
let serviceAccount = Test.getAccount(0x1654653399040a61)
_61
_61
// Check initial balance
_61
let initialBalanceScript = Test.executeScript(
_61
Test.readFile("../scripts/CheckBalance.cdc"),
_61
[serviceAccount.address]
_61
)
_61
Test.expect(initialBalanceScript, Test.beSucceeded())
_61
let initialBalance = initialBalanceScript.returnValue! as! UFix64
_61
_61
// Create a test recipient account and set up FlowToken vault
_61
let recipient = Test.createAccount()
_61
_61
// Set up the recipient's FlowToken vault
_61
let setupResult = Test.executeTransaction(
_61
Test.Transaction(
_61
code: Test.readFile("../transactions/SetupFlowTokenVault.cdc"),
_61
authorizers: [recipient.address],
_61
signers: [recipient],
_61
arguments: []
_61
)
_61
)
_61
Test.expect(setupResult, Test.beSucceeded())
_61
_61
// Execute transaction AS the mainnet service account
_61
// This works because fork testing allows impersonating any account
_61
let txResult = Test.executeTransaction(
_61
Test.Transaction(
_61
code: Test.readFile("../transactions/TransferTokens.cdc"),
_61
authorizers: [serviceAccount.address],
_61
signers: [serviceAccount],
_61
arguments: [10.0, recipient.address]
_61
)
_61
)
_61
_61
Test.expect(txResult, Test.beSucceeded())
_61
_61
// Verify the sender's balance decreased
_61
let newBalanceScript = Test.executeScript(
_61
Test.readFile("../scripts/CheckBalance.cdc"),
_61
[serviceAccount.address]
_61
)
_61
Test.expect(newBalanceScript, Test.beSucceeded())
_61
let newBalance = newBalanceScript.returnValue! as! UFix64
_61
_61
// Balance should have decreased by exactly the transfer amount
_61
Test.assertEqual(initialBalance - 10.0, newBalance)
_61
_61
// Verify the recipient received the tokens
_61
let recipientBalanceScript = Test.executeScript(
_61
Test.readFile("../scripts/CheckBalance.cdc"),
_61
[recipient.address]
_61
)
_61
Test.expect(recipientBalanceScript, Test.beSucceeded())
_61
let recipientBalance = recipientBalanceScript.returnValue! as! UFix64
_61
// Recipient should have at least 10.0 (may be slightly more due to storage refunds)
_61
Test.assert(recipientBalance >= 10.0, message: "Recipient should have at least 10 FLOW")
_61
}

Key Points About Account Impersonation

  1. Any account can be used: Call Test.getAccount(address) with any mainnet address
  2. No private keys needed: Fork testing has built-in impersonation—you can sign transactions as any account
  3. Real account state: The account has its actual mainnet balance, storage, and capabilities
  4. Mutations are local: Changes only affect your test environment, not the real network
  5. Test complex scenarios: Impersonate whale accounts, protocol accounts, or any user to test edge cases

Run All Tests Together

Now that you have multiple test files, run them all against the forked network:


_10
flow test --fork mainnet

This runs all *_test.cdc files in your project against mainnet. You should see:


_10
Test results: "cadence/tests/flow_token_test.cdc"
_10
- PASS: testFlowTokenSupplyIsPositive
_10
_10
Test results: "cadence/tests/token_checker_test.cdc"
_10
- PASS: testCheckBalanceOnRealAccount
_10
- PASS: testHasMinimumBalance
_10
- PASS: testTransactionAsMainnetAccount

Additional Options

You can also fork from testnet (flow test --fork testnet) or pin to a specific block height (--fork-height). See the Fork Testing Flags reference for all available options.

See also:

info

External oracles and off-chain systems

Fork tests run against Flow chain state only:

  • No live off-chain/API calls or cross-chain reads
  • Price feeds, bridges, indexers, and similar must be mocked (stub contracts or fixtures)
  • For end-to-end, combine with flow emulator --fork and a local stub service

Select tests quickly

  • Run specific files or directories:

_10
flow test cadence/tests/flow_token_test.cdc cadence/tests/token_checker_test.cdc --fork mainnet

  • Optional: narrow by function name with --name:

_10
flow test cadence/tests/token_checker_test.cdc --name _smoke --fork mainnet

  • Optional: suffix a few functions with _smoke for quick PR runs; run the full suite nightly or on protected branches.

When to Use Fork Testing

Fork testing is most valuable for:

  • Integration testing with real onchain contracts and data
  • Pre-deployment validation before mainnet releases
  • Upgrade testing against production state
  • Reproducing issues at a specific block height
  • Testing interactions with high-value or protocol accounts
  • Validating contract behavior with real-world data patterns

For strategy, limitations, and best practices, see the guide: Testing Smart Contracts.

Conclusion

In this tutorial, you learned how to use fork testing to validate your Cadence contracts against live Flow network state. You created tests that read from real mainnet contracts, deployed custom contracts that interact with production data, and executed transactions using account impersonation—all without deploying to a live network or bootstrapping test accounts.

Now that you have completed this tutorial, you should be able to:

  • Run Cadence tests against forked networks using flow test --fork
  • Test contracts that depend on real mainnet contracts without manual setup
  • Use account impersonation to execute transactions as any mainnet account
  • Read from production blockchain state in your test suite
  • Pin tests to specific block heights for historical debugging
  • Integrate fork testing into your development workflow

Fork testing bridges the gap between local unit tests and testnet deployments, enabling you to catch integration issues early and test against real-world conditions. Use it as part of your pre-deployment validation process, alongside emulator unit tests for determinism and isolation, and testnet deployments for final verification.

Next Steps

  • Explore additional assertions and helpers in the Cadence Testing Framework
  • Add more real-world tests that read from standard contracts like Flow NFT
  • Keep unit tests on the emulator for determinism and isolation; run forked integration tests selectively in CI
  • Review the Fork Testing Flags reference for advanced options
  • Learn about Flow Networks and public access nodes