Unit Testing & test_scenario
Run with sui move test or aptos move test.
25.1 Basic tests
Run with sui move test or aptos move test. Filter by name: sui move test reset.
#[test_only]
module counter::counter_tests;
use counter::counter;
#[test]
fun test_math() {
assert!(1 + 1 == 2);
}
const EMyError: u64 = 7;
#[test]
#[expected_failure(abort_code = EMyError)]
fun test_expected_abort() {
// Codes from other modules can be referenced by path,
// e.g. abort_code = counter::ENotOwner, or as a literal with location = counter
abort EMyError
}
#[test]
#[expected_failure(arithmetic_error, location = Self)]
fun test_overflow() {
let x: u8 = 255;
let _ = x + 1;
}25.2 Test attributes
A test function (no parameters on Sui; Aptos may take signer params) #[test(alice = @0xA, bob = @0xB)]
Aptos: inject signers for given addresses Test passes only if it aborts #[expected_failure(abort_code = E)]...with a specific code (and optionally location = module)
#[expected_failure(arithmetic_error)], (vector_error, minor_status = 1), (out_of_gas) Specific VM failures
Item compiled only for tests (helpers, test modules, test constructors)
Sui: run with randomly generated arguments
25.3 Test-only helpers in production modules
On Sui, std::unit_test::destroy(x) (older: sui::test_utils::destroy) disposes of any value in tests.
#[test_only]
public fun init_for_testing(ctx: &mut TxContext) { init(ctx) }
#[test_only]
public fun destroy_for_testing(c: Counter) {
let Counter { id, owner: _, value: _ } = c;
id.delete();
}25.4 Sui test_scenario: multi-transaction tests
test_scenario API begin(addr) / end() Start/finish a scenario
Finish the current tx, start a new one as addr &mut TxContext for the current tx
take_from_sender<T>() / return_to_sender(obj) Owned objects of the sender
take_from_address<T>(addr) / return_to_address(addr, obj) Owned objects of another address
take_shared<T>() / return_shared(obj) take_immutable<T>() / return_immutable(obj) Check an object was transferred Effects of next_tx
Returns TransactionEffects with created/transferred/shared IDs and emitted event counts
System objects in tests: sui::clock::create_for_testing(ctx), clock.increment_for_testing(ms), clock.destroy_for_testing(); sui::random::create_for_testing (via the system address); coin::mint_for_testing<T>(amount, ctx).
#[test_only]
module counter::scenario_tests;
use sui::test_scenario;
use counter::counter::{Self, Counter};
#[test]
fun test_shared_counter() {
let alice = @0xA;
let bob = @0xB;
let mut scenario = test_scenario::begin(alice);
// tx 1: Alice creates and shares the counter
counter::create(scenario.ctx());
// tx 2: Bob increments it
scenario.next_tx(bob);
{
let mut c = scenario.take_shared<Counter>();
c.increment();
assert!(c.value() == 1);
test_scenario::return_shared(c);
};
// tx 3: Alice resets it
scenario.next_tx(alice);
{
let mut c = scenario.take_shared<Counter>();
c.reset(scenario.ctx());
assert!(c.value() == 0);
test_scenario::return_shared(c);
};
scenario.end();
}25.5 Aptos tests with signers and framework setup
Useful Aptos test helpers: timestamp::set_time_has_started_for_testing(&aptos_framework_signer), timestamp::fast_forward_seconds(n), coin test initializers via aptos_coin::initialize_for_test, randomness::initialize_for_testing.
#[test_only]
module my_addr::counter_tests {
use std::signer;
use my_addr::counter;
#[test(user = @0x123)]
fun test_create_and_increment(user: &signer) {
let addr = signer::address_of(user);
aptos_framework::account::create_account_for_test(addr);
counter::create(user);
counter::increment(user);
assert!(counter::value(addr) == 1, 0);
}
#[test(user = @0x123)]
#[expected_failure(abort_code = 1, location = my_addr::counter)]
fun test_double_create_fails(user: &signer) {
counter::create(user);
counter::create(user);
}
}25.6 Coverage and CI
sui move test --coverage then sui move coverage summary / sui move coverage source --module m.
aptos move test --coverage then aptos move coverage summary.
Run tests, the linter, and (for critical code) the prover in CI on every commit.
🧪 Part II Exercises
Write a Library module with a Book struct (title, author, year) and getters; prove from a second module that you cannot construct Book directly.
Create a generic Stack<T: store> backed by a vector with push, pop, peek, size, and destroy_empty. Test it with a resource type.
Explain why public struct Wallet has drop { coins: vector<Coin<SUI>> } fails to compile, and fix it two different ways.
Implement Money<phantom Currency> with add and split; show that adding Money<USD> to Money<EUR> is a compile error.
Model a traffic light as an enum with a next function and a match; write tests covering every transition.
Write a Sui macro sum_by! that sums $f(x) over a vector reference, and an equivalent Aptos inline function with a lambda.
Rewrite a Diem-style module (no method syntax, vector::borrow, friend) to Sui Move 2024 idioms (public(package), method calls, index syntax, let mut).
Add #[error] constants (Sui) or std::error categories (Aptos) to the counter module, and write #[expected_failure] tests for each.
Emit an event on every counter increment; on your chain's testnet, query the events with the CLI or explorer.
Write a test_scenario test (Sui) or a multi-signer test (Aptos) where Alice creates an item, transfers it to Bob, and Bob verifies ownership.