Post
Share your knowledge.
Best Practices for Testing Sui Move Modules Locally?
I need to thoroughly test my Sui Move smart contracts before deployment, ensuring: full coverage of edge cases, deterministic results across environments, gas cost optimization validation and integration safety with other modules
Current Challenges:
- Basic unit tests pass but miss complex scenarios
- Struggling with mock objects and external dependencies
- Need to benchmark gas usage accurately
Questions:
- What’s the optimal test structure for Move modules?
- How to simulate real-world conditions (multi-tx sequences)?
- Best tools for gas profiling?
- How to test failure modes effectively?
- Sui
- Move
Answers
61. Core Testing Framework
Standard Test Layout
my_module/
├── sources/
│ └── main.move
└── tests/
├── unit_tests.move
├── integration_tests.move
└── gas_benchmarks.move
Essential Annotations:
#[test_only] // Marks test helpers
#[expected_failure(abort_code = EInvalid)] // Negative testing
#[test] // Actual test cases
Example Unit Test
#[test]
fun test_token_mint() {
let ctx = &mut tx_context::dummy();
let cap = test_coins::mint_cap(ctx);
let coin = coin::mint(&cap, 100, ctx);
assert!(coin::value(&coin) == 100, EBadMint);
}
2. Advanced Testing Techniques
Scenario Testing (Multi-Tx Flow)
#[test]
fun test_auction_flow() {
let scenario = test_scenario::begin(@0x123);
// Setup
test_scenario::next_tx(&mut scenario, @admin);
auction::create(&mut scenario);
// Bid
test_scenario::next_tx(&mut scenario, @bidder1);
auction::bid(100);
// Close
test_scenario::next_tx(&mut scenario, @admin);
auction::close();
test_scenario::end(scenario);
}
Mocking Critical Dependencies
#[test_only]
module fake_oracle {
public fun get_price(): u64 { 150 } // Fixed test value
}
#[test]
fun test_price_sensitive_logic() {
let price = fake_oracle::get_price(); // Overrides real oracle
// Test behavior at price=150
}
3. Gas Optimization Testing
Benchmarking Tools
sui move test --gas-stats # Shows per-test gas usage
Key Metrics:
- Storage costs: Object creation/updates
- Computation: Loops and complex math
- Memory: Large vector operations
Gas Profiling Example
#[test]
fun benchmark_bulk_transfer() {
let ctx = &mut tx_context::dummy();
let start_gas = gas::remaining(ctx);
// Operation being measured
bulk_transfer(1000, ctx);
let used_gas = start_gas - gas::remaining(ctx);
assert!(used_gas < MAX_EXPECTED_GAS, EGasSpike);
}
**4. Negative Testing Patterns
Expected Failures
#[test]
#[expected_failure(abort_code = EAccessDenied)]
fun test_admin_only_fail() {
let ctx = &mut tx_context::dummy();
access_control::admin_action(&signer(@user), ctx); // Non-admin
}
Fuzz Testing
#[test_only]
fun fuzz_transfer_amount(amount: u64) {
let ctx = &mut tx_context::dummy();
let balance = 1000;
transfer(&mut balance, amount); // Tests 0, MAX, etc.
}
5. CI/CD Integration
GitHub Actions Setup
- name: Run Move tests
run: sui move test --coverage
- name: Verify coverage
run: |
grep "100.00%" coverage_summary.md || exit 1
Critical Checks
sui move prove # Formal verification
sui move build --lint # Style/security checks
6. Debugging Tips
Interactive Debugging
sui move test -i # Enters debug console on failure
Event Inspection
#[test]
fun test_event_emission() {
let scenario = test_scenario::begin(@0x1);
// Trigger event
let events = test_scenario::take_events<MyEvent>(scenario);
assert!(vector::length(&events) == 1, EEventMissing);
}
For optimal testing, structure your Move tests into unit tests (within the module using #[test_only] functions) and integration tests (separate modules) focusing on specific functionalities or contracts. To simulate real-world conditions and multi-transaction sequences, leverage the sui-test-runner within your Move tests directly; it allows scripting complex scenarios and interaction patterns. For accurate gas profiling, the sui client dry-run command is your go-to for individual transactions, and for programmatic testing, use sui-move test --gas-profiler to get detailed gas reports. To effectively test failure modes, write tests that specifically trigger expected aborts (e.g., via assert! or explicit abort) and verify the correct error code is returned, ensuring your contract handles invalid inputs or states gracefully.
Here’s a concise guide to best practices for testing Sui Move modules locally:
✅ Optimal Test Structure
- Organize by feature: Group tests per module function (e.g.,
transfer,mint, etc.). - Use
#[test]in.move: For pure logic/unit tests. - Write
sui move test+dev-inspect: For integration-style testing with real tx behavior.
🧪 Simulating Real-World Conditions
- Use
sui clientCLI ordevnetto chain real transactions. - For automation: Use Typescript SDK to script multi-tx flows.
- Emulate multiple addresses, coin transfers, object state changes.
⛽ Gas Profiling Tools
sui move test --gas-report: For per-function gas breakdown.sui client dry-runwith--gas-budget: Simulate actual costs.- Track over multiple iterations to catch regressions.
🚨 Testing Failure Modes
-
Write tests expecting panics: use
expect_abortin Move. -
Test against:
- Invalid input/state
- Unauthorized access
- Boundary conditions (e.g., 0 amount, max cap)
-
Consider fuzz testing (WIP in community).
🧩 Mocks & Dependencies
- Create test-only modules for mock behaviors.
- Use Sui’s
TestScenariofeature (if using Typescript or SDK) to isolate environments.
Let me know if you want a working test scaffold template.
To thoroughly test Sui Move modules locally, write comprehensive unit and integration tests within the Move code using #[test] and #[test_only] modules. Use the sui move test command to run tests with 100% coverage, including edge cases like zero values and invalid addresses. For mock objects and external dependencies, define test-only modules that simulate dependencies, and use std::option or custom error codes to validate revert conditions. To benchmark gas, analyze transaction effects after execution using the Sui CLI or SDK to measure gas used in test transactions. Ensure deterministic results by avoiding reliance on sui::clock in tests or mocking it when necessary. Test integration safety by publishing dependent modules in the test environment and verifying cross-module calls. Always run sui move lint and inspect object ownership transitions to catch issues early.
to thoroughly test Sui Move modules, start by organizing your tests using the Move.toml file with well-structured test targets. Use #[test] functions in each module to cover both success and failure cases with clear assertions. Simulate real-world conditions by chaining multiple function calls in a single test, mimicking multi-transaction workflows. For external dependencies, use test-only modules and mock objects within the same package to isolate logic. Leverage the sui move test command with custom gas settings to assess performance under constrained conditions. Use --gas-report to gather detailed gas profiling and compare changes across versions. Test edge cases by deliberately passing malformed or boundary inputs to check robustness. Enable negative testing with assert_abort! to confirm proper error handling and failure modes. For integration scenarios, create test packages that import multiple modules and simulate cross-module interactions. Run tests frequently and in different environments to ensure deterministic behavior across platforms.
Do you know the answer?
Please log in and share it.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
- How to Maximize Profit Holding SUI: Sui Staking vs Liquid Staking616
- Why does BCS require exact field order for deserialization when Move structs have named fields?65
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution55
- Sui Move Error - Unable to process transaction No valid gas coins found for the transaction419
- Sui Transaction Failing: Objects Reserved for Another Transaction410