Skip to main content

Build and Test

In this lesson, you'll add test code to my_first_package.move and learn how to verify its behavior locally. It's not difficult — just add a test function to the same file and you can check it right away.


Prerequisites


Why write tests?

Contracts deployed to the blockchain are essentially immutable — though Sui does support package upgrades, which will be covered in a later lesson. To catch bugs before deployment, it's important to run tests locally.

Move has built-in testing support — you just use the #[test] annotation to define a test function. You can run it as many times as you want locally, without ever deploying the contract.


Add the test code

Open sources/my_first_package.move and add the following test code at the end of the file (before the closing brace of the module).

sources/my_first_package.move
module my_first_package::counter {
/// Counter object
public struct Counter has key {
id: UID,
value: u64,
}

/// Create a counter and transfer it to the sender
entry fun create(ctx: &mut TxContext) {
let counter = Counter {
id: object::new(ctx),
value: 0,
};
transfer::transfer(counter, ctx.sender());
}

/// Increment the counter value by 1
entry fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}

// === Tests ===

#[test]
fun test_increment() {
// Create a dummy context for testing
let mut ctx = tx_context::dummy();

// Create a Counter object directly (private fields are accessible within the same module)
let mut counter = Counter {
id: object::new(&mut ctx),
value: 0,
};

// Run increment
increment(&mut counter);

// Verify the value is now 1
assert!(counter.value == 1, 0);

// Clean up the object at the end of the test
let Counter { id, value: _ } = counter;
object::delete(id);
}
}

Key points of the test code

#[test] annotation

Functions marked with #[test] only run when you execute sui move test. They are not included in a normal build.

tx_context::dummy()

Creates a dummy context for testing. In production, the Sui network creates the context, but in tests you use this dummy instead.

assert!(condition, error_code)

Fails the test if the condition is false. assert!(counter.value == 1, 0) means "error if value is not 1".

Cleanup (destructuring + object::delete)

In Move, you can't simply drop an object with has key when it goes out of scope. Inside tests, use object::delete(id) to explicitly delete it at the end.

Accessible because it's in the same module

The test function is written inside the same module as the contract, so it can directly access private fields like value. If you're testing from a different module, you'll need accessor functions or test_scenario.


Try It Out

You can try this in two ways — pick whichever suits you.

Option A: Move Playground

No local setup required. Paste the test code above into the Playground, then click Test to run sui move test directly in your browser.

my_first_package
Move.toml
sources
my_first_package.move
Move.toml
README.md
Console
Build ready.

Option B: VSCode + CLI

The [environments] setting in Move.toml was configured in L14. If you haven't done it yet, refer to Verify with a Build.

1. Run the build

Run the following from the project root directory (the folder containing Move.toml).

sui move build

A successful build looks like this:

INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_first_package

Once you see BUILDING my_first_package, the build itself is successful.

If you get an error, review the syntax of your test code. If you have the VSCode Sui Extension, errors will be highlighted in real time.

2. Run the tests

Once the build passes, run the tests.

sui move test

A successful test run looks like this:

INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_first_package
Running Move unit tests
[ PASS ] my_first_package::counter::test_increment
Test result: OK. Total tests: 1; passed: 1; failed: 0

If you see [ PASS ], you're done! You've confirmed locally that the increment function works correctly.

If the assert! condition isn't met, you'll see:

[ FAIL    ] my_first_package::counter::test_increment
Test result: FAILED. Total tests: 1; passed: 0; failed: 1

In that case, review the logic of the increment function or the condition in assert!.


Success checklist

You've completed this lesson when you can:

  • Added the test_increment test function to my_first_package.move
  • Compiled without errors (in Playground or via sui move build)
  • sui move test showed [ PASS ] (Playground Test button or CLI)

What you did in this lesson

  • Defined a test function with the #[test] annotation
  • Created a test context with tx_context::dummy()
  • Verified the expected value with assert!
  • Verified the contract locally with sui move build and sui move test