Skip to main content

Creating and Using System Objects in Tests

Some tests require system objects like Clock, Random, or DenyList. These objects have fixed addresses on the network and are created during genesis. In tests, they don't exist by default, so the Sui Framework provides #[test_only] functions to create and manipulate them.

Clock

The Clock provides the current network timestamp. Use clock::create_for_testing to create one, and manipulate time with test-only functions:

use std::unit_test::assert_eq;
use sui::clock;

#[test]
fun test_clock() {
let ctx = &mut tx_context::dummy();
let mut clock = clock::create_for_testing(ctx);

// Starts at 0
assert_eq!(clock.timestamp_ms(), 0);

// Add time (in milliseconds)
clock.increment_for_testing(1000);
assert_eq!(clock.timestamp_ms(), 1000);

// Set absolute time (must be >= current)
clock.set_for_testing(5000);
assert_eq!(clock.timestamp_ms(), 5000);

// Clean up - Clock doesn't have `drop`
clock.destroy_for_testing();
}

To share a Clock for use in a test scenario, call share_for_testing:

#[test]
fun test_shared_clock() {
let ctx = &mut tx_context::dummy();
let clock = clock::create_for_testing(ctx);
clock.share_for_testing();
}

Random

The Random object provides on-chain randomness. In tests, the full Random shared object can only be created inside a test scenario via random::create_for_testing. However, the preferred approach is to structure your code so that the core logic takes a RandomGenerator parameter - this lets you create a generator directly in unit tests with random::new_generator_for_testing(), bypassing the Random object entirely. This is easier to work with because Random requires an entry function (which cannot return non-droppable values), making it harder to assert on results.

use sui::random::{Self, Random, RandomGenerator};

// To use Random, a function must have `entry` modifier, hence it cannot return
// a value, and not so easy to test.
entry fun my_entry_function(r: &Random, ctx: &mut TxContext) {
let mut gen = random::new_generator(r, ctx);
let result = inner_function(&mut gen);
result.destroy_or!(abort);
}

// Example of an inner function that is easier to test than the entry point.
public(package) fun inner_function(gen: &mut RandomGenerator): Option<u64> {
if (gen.generate_bool()) {
option::some(gen.generate_u64())
} else {
option::none()
}
}

#[test]
fun test_simple_random() {
// Deterministic, always the same value.
let mut gen = random::new_generator_for_testing();
assert!(inner_function(&mut gen).is_none());

// Deterministic (reproducible with same seed)
let seed = b"Arbitrary seed bytes";
let mut gen = random::new_generator_from_seed_for_testing(seed);
assert!(inner_function(&mut gen).is_some());
}

For entry points that take the full Random shared object (the only possible way is to take it as a reference &Random), use a test scenario:

use sui::random::{Self, Random};
use sui::test_scenario;

#[test]
fun test_random_shared() {
let mut scenario = test_scenario::begin(@0x0);

// Create and share Random
random::create_for_testing(scenario.ctx());
scenario.next_tx(@0x0);

let mut random = scenario.take_shared<Random>();

// Initialize with seed bytes (required before use)
random.update_randomness_state_for_testing(
0,
x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F",
scenario.ctx(),
);

my_entry_function(&random, scenario.ctx());

test_scenario::return_shared(random);
scenario.end();
}

DenyList

The DenyList is used by regulated coins to block specific addresses. Create a local instance with new_for_testing, or a shared one with create_for_testing:

use sui::deny_list;
use sui::test_scenario;
use std::unit_test::destroy;

#[test]
fun test_deny_list() {
let mut scenario = test_scenario::begin(@0x0);

// Create a local instance for simple tests
let deny_list = deny_list::new_for_testing(scenario.ctx());
// ... use deny_list
destroy(deny_list);

// Or create a shared DenyList
deny_list::create_for_testing(scenario.ctx());
scenario.next_tx(@0x0);
// ... take_shared and use

scenario.end();
}

Coin and Balance

For testing with coins, use coin::mint_for_testing and balance::create_for_testing:

use std::unit_test::assert_eq;
use sui::coin;
use sui::balance;
use sui::sui::SUI;

#[test]
fun test_coins() {
let ctx = &mut tx_context::dummy();

// Create a coin of any type
let coin = coin::mint_for_testing<SUI>(1000, ctx);
assert_eq!(coin.value(), 1000);

// Destroy and get the value back
let value = coin.burn_for_testing();
assert_eq!(value, 1000);

// Create a balance directly
let balance = balance::create_for_testing<SUI>(500);
let value = balance.destroy_for_testing();
assert_eq!(value, 500);
}

Create All System Objects at Once

When using Test Scenario, you can create all system objects at once with create_system_objects. This creates and shares Clock, Random, and DenyList:

use sui::clock::Clock;
use sui::random::Random;
use sui::deny_list::DenyList;
use sui::test_scenario;

#[test]
fun test_with_all_system_objects() {
let mut scenario = test_scenario::begin(@0xA);

// Creates Clock, Random, and DenyList as shared objects
scenario.create_system_objects();
scenario.next_tx(@0xA);

// Take objects by type
let clock = scenario.take_shared<Clock>();
let random = scenario.take_shared<Random>();
let deny_list = scenario.take_shared<DenyList>();

// ... use the objects

// Return them when done
test_scenario::return_shared(clock);
test_scenario::return_shared(random);
test_scenario::return_shared(deny_list);

scenario.end();
}

System objects created in tests won't have the same fixed addresses they have on a live network. Use take_shared<T>() to access them by type rather than by ID.

To take a specific shared object by ID, use take_shared_by_id:

use sui::test_scenario::{Self, most_recent_id_shared};

#[test]
fun test_take_by_id() {
let mut scenario = test_scenario::begin(@0xA);
scenario.create_system_objects();
scenario.next_tx(@0xA);

// Get the ID of the most recent shared Clock
let clock_id = most_recent_id_shared<Clock>().destroy_some();

// Take by ID
let clock = scenario.take_shared_by_id<Clock>(clock_id);
// ...
test_scenario::return_shared(clock);

scenario.end();
}

Summary

ObjectCreationTest-only Features
Clockclock::create_for_testing(ctx)increment_for_testing, set_for_testing
Randomrandom::create_for_testing(ctx)update_randomness_state_for_testing
RandomGeneratorrandom::new_generator_for_testing()new_generator_from_seed_for_testing
DenyListdeny_list::create_for_testing(ctx)new_for_testing
Coin<T>coin::mint_for_testing<T>(value, ctx)burn_for_testing
Balance<T>balance::create_for_testing<T>(value)destroy_for_testing
All system objectsscenario.create_system_objects()Creates Clock, Random, DenyList