Skip to main content
The IOTA CLI provides comprehensive testing tools for Move packages, transactions, and network interactions.

Move Unit Testing

Running Tests

Execute Move unit tests:
iota move test [OPTIONS] [FILTER]

Basic Testing

1

Write tests

Create tests in your Move modules:
sources/my_module.move
module my_package::my_module {
    use iota::coin::Coin;
    use iota::iota::IOTA;
    use iota::test_scenario;

    #[test]
    fun test_create_and_transfer() {
        let mut scenario = test_scenario::begin(@0x1);
        
        // Test logic here
        
        test_scenario::end(scenario);
    }

    #[test]
    #[expected_failure(abort_code = EInsufficientBalance)]
    fun test_insufficient_balance() {
        // Test error case
    }
}
2

Run all tests

iota move test
Output:
INCLUDING DEPENDENCY Iota
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_package
Running Move unit tests
[ PASS    ] 0x0::my_module::test_create_and_transfer
[ PASS    ] 0x0::my_module::test_insufficient_balance
Test result: OK. Total tests: 2; passed: 2; failed: 0
3

Run specific tests

# Match by name pattern
iota move test "test_transfer"

# Run all tests in a module
iota move test "my_module::"

Test Options

Parallel Execution

iota move test --num-threads 4
Run tests in parallel for faster execution.

Gas Limits

iota move test --gas-limit 100000000
Set maximum gas per test (useful for expensive operations).

List Tests

iota move test --list
Display all available tests without running them:
0x0::my_module::test_create_and_transfer
0x0::my_module::test_insufficient_balance
0x0::my_module::test_upgrade
0x0::helpers::test_utils

Coverage

iota move test --coverage
Collect code coverage information during test execution.

Test Patterns

Basic Test

#[test]
fun test_simple_operation() {
    let value = 42;
    assert!(value == 42, 0);
}

Expected Failure

#[test]
#[expected_failure(abort_code = EInvalidAmount)]
fun test_invalid_amount() {
    // This test passes if it aborts with EInvalidAmount
    abort EInvalidAmount
}

Test-Only Functions

#[test_only]
public fun create_test_object(ctx: &mut TxContext): TestObject {
    TestObject { id: object::new(ctx) }
}

#[test]
fun test_with_helper() {
    let ctx = tx_context::dummy();
    let obj = create_test_object(&mut ctx);
    // Test obj...
}

Test Scenarios

use iota::test_scenario::{Self as ts, Scenario};

#[test]
fun test_multi_transaction() {
    let admin = @0xAD;
    let user = @0x1;
    
    let mut scenario = ts::begin(admin);
    
    // Transaction 1: Admin creates object
    {
        create_object(ts::ctx(&mut scenario));
    };
    
    // Transaction 2: User interacts
    ts::next_tx(&mut scenario, user);
    {
        let obj = ts::take_shared<MyObject>(&scenario);
        use_object(&mut obj);
        ts::return_shared(obj);
    };
    
    ts::end(scenario);
}

Transaction Testing

Dry Run

Simulate transaction execution without committing:
iota client call [ARGS] --dry-run

Example

iota client call \
  --package 0x2 \
  --module coin \
  --function transfer \
  --type-args 0x2::iota::IOTA \
  --args 0xCOIN 0xRECIPIENT 1000 \
  --gas-budget 10000000 \
  --dry-run
Output:
{
  "effects": {
    "status": { "status": "success" },
    "gasUsed": {
      "computationCost": "1000000",
      "storageCost": "2000000",
      "storageRebate": "500000"
    }
  },
  "events": [],
  "balanceChanges": [
    {
      "owner": "0x...",
      "coinType": "0x2::iota::IOTA",
      "amount": "-1000"
    }
  ]
}

Dev Inspect

Inspect transaction execution with detailed debugging:
iota client call [ARGS] --dev-inspect

Example

iota client call \
  --package 0xPKG \
  --module my_module \
  --function debug_function \
  --args 0xOBJ \
  --dev-inspect
Output includes:
  • Return values
  • Events emitted
  • Gas usage
  • Object changes
  • Error details (if any)

Transaction Digest

Compute transaction digest without executing:
iota client call [ARGS] --tx-digest
Output:
Transaction Digest: 5KzKVq7qP2xH8FwkQJqxhPNBjkFVjM3xZoJpP5KwqAkH

Serialized Transactions

Serialize transaction for later execution:
1

Serialize unsigned

iota client call \
  --package 0x2 \
  --module coin \
  --function transfer \
  --type-args 0x2::iota::IOTA \
  --args 0xCOIN 0xRECIPIENT 1000 \
  --gas-budget 10000000 \
  --serialize-unsigned-transaction
Output:
AAACAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAWNvaW4IdHJhbnNmZXIBAQAAAQEA...
2

Sign transaction

iota keytool sign \
  --address alice \
  --data AAACA...
3

Execute signed transaction

iota client execute-signed-tx \
  --tx-bytes AAACA... \
  --signatures AQEDAgQF...

Local Network Testing

Quick Test Cycle

1

Start local network

# Terminal 1
iota start --force-regenesis --with-faucet
2

Setup client

# Terminal 2
iota client new-env --alias localnet --rpc http://127.0.0.1:9000
iota client switch --env localnet
iota client new-address ed25519
iota client faucet
3

Test package

cd my_package
iota move test
iota client publish --gas-budget 100000000
4

Test interactions

iota client call \
  --package 0xPKG \
  --module my_module \
  --function test_function \
  --gas-budget 10000000
5

Iterate

# Make changes to code
iota move test

# Restart for clean state
# Terminal 1: Ctrl+C, then:
iota start --force-regenesis --with-faucet

# Terminal 2: Republish
iota client publish --gas-budget 100000000

Persistent Testing

Test with state preservation:
1

Create persistent network

iota genesis --force --with-faucet
iota start
2

Publish v1

iota client publish --gas-budget 100000000
# Note Package ID and UpgradeCap ID
3

Test v1

iota client call --package 0xPKG --module my_module --function v1_function
4

Modify and upgrade

# Edit code
iota move test

# Upgrade
iota client upgrade \
  --upgrade-capability 0xCAP \
  --gas-budget 100000000
5

Test v2

iota client call --package 0xNEW_PKG --module my_module --function v2_function

Integration Testing

Multi-Package Testing

Test interactions between multiple packages:
1

Publish dependencies

cd package_a
iota client publish --gas-budget 100000000
# Note Package A ID

cd ../package_b
iota client publish --gas-budget 100000000
# Note Package B ID
2

Update main package

Move.toml
[dependencies]
PackageA = { address = "0xPKG_A_ID" }
PackageB = { address = "0xPKG_B_ID" }
3

Test integration

cd ../main_package
iota move test
iota client publish --gas-budget 100000000

Multi-Signature Testing

Test MultiSig transactions:
1

Create MultiSig address

iota keytool multisig-address \
  --threshold 2 \
  --pks <PK1> <PK2> <PK3> \
  --weights 1 1 1
# Note MultiSig address
2

Fund MultiSig

iota client transfer \
  --to 0xMULTISIG \
  --object-id 0xCOIN \
  --gas-budget 5000000
3

Create transaction

iota client call \
  --package 0x2 \
  --module coin \
  --function transfer \
  --args 0xMULTISIG_COIN 0xRECIPIENT 1000 \
  --gas-budget 10000000 \
  --sender 0xMULTISIG \
  --serialize-unsigned-transaction
# Save TX_BYTES
4

Collect signatures

# Signer 1
iota keytool sign --address alice --data <TX_BYTES>
# Save SIG1

# Signer 2
iota keytool sign --address bob --data <TX_BYTES>
# Save SIG2
5

Combine and execute

iota keytool multisig-combine-partial-sig \
  --sigs <SIG1> <SIG2> \
  --pks <PK1> <PK2> <PK3> \
  --weights 1 1 1 \
  --threshold 2
# Save MULTISIG

iota client execute-signed-tx \
  --tx-bytes <TX_BYTES> \
  --signatures <MULTISIG>

Replay Testing

Replay Transactions

Replay past transactions for debugging:
iota client replay-transaction \
  --tx-digest <DIGEST> \
  --gas-info \
  --ptb-info
Output:
Replaying transaction: 5KzKVq7qP2xH8FwkQJqxhPNBjkFVjM3xZoJpP5KwqAkH

Effects:
  Status: Success
  Gas Used: 1000000 NANOS
  
PTB Commands:
  0: MoveCall 0x2::coin::transfer
  1: TransferObjects

Events:
  CoinBalanceChange { ... }

Replay Batches

Replay multiple transactions:
1

Create digest file

cat > digests.txt << EOF
5KzKVq7qP2xH8FwkQJqxhPNBjkFVjM3xZoJpP5KwqAkH
7MpRxT9uU4zL0NqEqO5kWgYiDcFxOsZrPbMdNhKsRcKo
9PrSyV0wX5aM1OrGsQ6mYiZkEgHzQvCsTdOfPjMuTeLq
EOF
2

Replay batch

iota client replay-batch \
  --path digests.txt \
  --terminate-early

Replay Checkpoints

Replay transactions from checkpoint range:
iota client replay-checkpoint \
  --start 100 \
  --end 200 \
  --terminate-early

Coverage Analysis

Collect Coverage

1

Run tests with coverage

iota move test --coverage
2

View coverage summary

iota move coverage --summary
Output:
Module 0x0::my_module
├── % covered: 87.5
├── uncovered locations:
│   ├── my_module.move:42
│   └── my_module.move:58

Module 0x0::helpers
├── % covered: 100.0
└── uncovered locations: none
3

Export to CSV

iota move coverage --output-csv coverage.csv
4

Analyze specific module

iota move coverage --module my_module

Gas Profiling

Profile Transaction

iota client profile-transaction \
  --tx-digest <DIGEST> \
  --profile-output ./profile.json
This generates a flamegraph-compatible JSON file showing gas usage breakdown.

Analyze Profile

1

Generate profile

iota client profile-transaction \
  --tx-digest 5KzKVq7qP2xH8FwkQJqxhPNBjkFVjM3xZoJpP5KwqAkH \
  --profile-output ./gas_profile.json
2

View in speedscope

Open https://www.speedscope.app/ and load gas_profile.json.

Bytecode Verification

Verify Meter

Check bytecode complexity limits:
iota client verify-bytecode-meter [OPTIONS]

Options

  • --package <PATH> - Package to verify
  • --protocol-version <VERSION> - Protocol version
  • --module <PATH>... - Specific modules to verify

Examples

1

Verify package

iota client verify-bytecode-meter --package ./my_package
Output:
Verifying package: my_package
Module my_module: OK (12345/16000 units)
Module helpers: OK (3456/16000 units)
All modules passed bytecode verification
2

Verify specific module

iota client verify-bytecode-meter \
  --module ./build/my_package/bytecode_modules/my_module.mv
3

Check against protocol version

iota client verify-bytecode-meter \
  --package ./my_package \
  --protocol-version 40

Verify Source

Verify on-chain bytecode matches local source:
iota client verify-source [OPTIONS] [PACKAGE_PATH]

Options

  • --verify-deps - Also verify dependencies
  • --skip-source - Only verify dependencies
  • --address-override <ADDRESS> - Override package address

Example

iota client verify-source ./my_package --verify-deps
Output:
Verifying package: my_package (0xPKG)
  Module my_module: OK
  Module helpers: OK

Verifying dependencies:
  Iota (0x2): OK
  MoveStdlib (0x1): OK

All packages verified successfully

Testing Best Practices

Unit Test Guidelines

  1. Test all public functions:
    #[test]
    fun test_public_function() { /* ... */ }
    
  2. Test error conditions:
    #[test]
    #[expected_failure(abort_code = EError)]
    fun test_error_case() { /* ... */ }
    
  3. Use test-only helpers:
    #[test_only]
    public fun setup_test() { /* ... */ }
    
  4. Test scenarios:
    #[test]
    fun test_multi_tx_scenario() {
        let mut scenario = test_scenario::begin(@0x1);
        // Multi-transaction test
        test_scenario::end(scenario);
    }
    

Integration Test Workflow

  1. Start clean network:
    iota start --force-regenesis --with-faucet
    
  2. Deploy all packages:
    # Deploy in dependency order
    cd base_package && iota client publish
    cd ../dependent_package && iota client publish
    
  3. Run integration tests:
    ./test_integration.sh
    
  4. Verify state:
    iota client object <OBJECT_ID>
    

Testnet Testing

  1. Deploy to testnet:
    iota client switch --env testnet
    iota client publish --gas-budget 100000000
    
  2. Automated testing:
    # Create test script
    ./run_testnet_tests.sh
    
  3. Monitor results:
    iota client tx-block <DIGEST>
    

Continuous Integration

Integrate testing into CI/CD:
.github/workflows/test.yml
name: Test
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Install IOTA CLI
        run: |
          cargo install --path crates/iota
      
      - name: Run Move tests
        run: |
          cd my_package
          iota move test
      
      - name: Start local network
        run: |
          iota start --force-regenesis &
          sleep 10
      
      - name: Integration tests
        run: |
          ./run_integration_tests.sh

Troubleshooting

Test Failures

Error: Test exceeded gas limit Solution:
iota move test --gas-limit 200000000
Error: Test timed out Check for infinite loops or expensive operations.

Dry Run Issues

Error: Insufficient gas Solution:
# Increase gas budget
iota client call [...] --gas-budget 20000000 --dry-run
Error: Object not found Verify object exists and is owned by sender:
iota client object <OBJECT_ID>

Network Issues

Error: Connection refused Ensure network is running:
# Start network
iota start --force-regenesis

# Verify connection
curl http://127.0.0.1:9000

Next Steps

Move Commands

Build and publish packages

Transaction Builder

Create complex transactions

Local Network

Set up testing environment

Client Commands

Interact with the network

Build docs developers (and LLMs) love