Test Rell code
To write unit tests for Rell code, use test module. You need to use the @test annotation to define
a test module.
@test module;
function test_foo() {
assert_equals(2 + 2, 4);
}
function test_bar() {
assert_equals(2 + 2, 5);
}
All functions in a test module that start with test_ (and a function called exactly test) are test functions that're
executed when you run the test module.
If the module has the "_test" suffix, it becomes a test module for the module that bears the same name without the
suffix. For example, if the module name is program, the test module is program_test.
Test names should clearly state what is being tested, not how. This improves readability and makes test reports easier to understand.
-
✅ Prefix test names with
test_. -
✅ Focus on business scenarios.
-
🚫 Avoid redundant words like
it_should_or_successful.// 🚫 Unclear and verbose
test_it_should_get_user
test_return_paginated_users_list_by_id_successful
// ✅ Clear and focused
test_get_full_user_profile_by_id
test_add_ipfs_link_as_user_profile_avatar
To run a test module, use the test command (chr test):
chr test --settings chromia.yml --modules my_test_module
For more information, see the chr test topic. Each test function gets executed independently of others, and a summary gets printed at the end:
TEST RESULTS:
my_test_module:test_foo OK
my_test_module:test_bar FAILED
SUMMARY: 1 FAILED / 1 PASSED / 2 TOTAL
***** FAILED *****
Test module (file data_test.rell):
@test module;
import data;
function test_add_user() {
assert_equals(data.user @* {}(.name), list<text>());
val tx = rell.test.tx(data.add_user('Bob'));
assert_equals(data.user @* {}(.name), list<text>());
tx.run();
assert_equals(data.user @* {}(.name), ['Bob']);
}
-
✅ Use descriptive variable names that clearly reflect user roles or test scenarios.
// 🚫 Unclear naming
val (admin_user, _) = owned_tests_utils.create_test_user(admin);
val (alice_user, _) = owned_tests_utils.create_test_user(alice);
// ✅ Clear and meaningful naming
val (admin, _) = owned_tests_utils.create_test_user(admin);
val (alice, _) = owned_tests_utils.create_test_user(alice); -
✅ Ensure comprehensive test coverage for entity variations.
When testing queries like:
q @* { q.user == user, q.project == project }Make sure to:
- Test multiple user and project combinations.
- Vary configurations to cover edge cases.
- Simulate realistic, business-driven scenarios.
Example of a test module
Here's an example of a test module:
@test module;
import main;
function test_init(){
assert_equals((main.user @* {}(.username)).size(),0);
assert_equals((main.balance @* {}(.user)).size(),0);
rell.test.tx().op(main.init(rell.test.pubkeys.alice)).run();
assert_equals(main.user @* {}(.username).size(),1);
assert_equals(main.balance@{.user == main.user @ { .pubkey == rell.test.pubkeys.alice}}(.amount),1000000);
}
function test_register_user(){
rell.test.tx().op(main.init(rell.test.pubkeys.alice)).run();
assert_equals(main.user @* {}(.username).size(),1);
rell.test.tx().op(main.register_user(rell.test.pubkeys.alice,rell.test.pubkeys.bob,"bob",100)).sign(rell.test.keypairs.alice).run();
assert_equals(main.user @* {}(.username).size(),2);
assert_equals(main.user @ {.pubkey == rell.test.pubkeys.bob}(.username),"bob");
assert_equals(main.balance@{.user == main.user @ {.pubkey == rell.test.pubkeys.bob}}(.amount),100);
}
function test_blocks(){
val tx1 = rell.test.tx().op(main.init(rell.test.pubkeys.alice));
val tx2 = rell.test.tx().op(main.register_user(rell.test.pubkeys.alice,rell.test.pubkeys.bob,"bob",100)).sign(rell.test.keypairs.alice);
val tx3 = rell.test.tx().op(main.create_channel(rell.test.pubkeys.alice,"channel 1")).sign(rell.test.keypairs.alice);
rell.test.block().tx(tx1).tx(tx2).tx(tx3).run();
val tx4 = rell.test.tx().op(main.add_channel_member(rell.test.pubkeys.alice,"channel 1","bob")).sign(rell.test.keypairs.alice);
rell.test.block().tx(tx4).run();
}
Unit tests also display the duration of each test case and the overall test suite duration.
TEST RESULTS:
OK tests.foobar:test_foo (0.005s)
FAILED tests.foobar:test_fail_require (0.001s)
FAILED tests.foobar:test_fail_assert_equals (0.001s)
SUMMARY: 2 FAILED / 1 PASSED / 3 TOTAL (0.007s)
✅ Focus each negative test case on one specific failure scenario for clarity.
✅ Use consistent, descriptive naming conventions for failure tests, e.g function test_internal_transfer_fails.
✅ Use provided helper functions to register users efficiently:
val admin = register_admin();
val alice = register_alice();
val bob = register_bob();
🚫 Avoid verbose setup patterns:
val admin_account = create_ft3_test_acc(admin_keypair());
✅ When using run_must_fail(), always include an expected error message to make failure reasons explicit:
// Without error message – failure reason unclear
owned.credits_external.create_credit_role("Role")
.sign(admin.keypair)
.run_must_fail();
// With error message – failure reason clear
owned.credits_external.create_credit_role("Role")
.sign(admin.keypair)
.run_must_fail("Failed to create credit role that already exists");
Testing framework overview
The Rell testing framework provides tools for creating and executing test blocks, transactions, and operations. For complete API documentation, see the rell.test namespace reference.
Test builders
Test code uses builder types to construct blocks, transactions, and operations:
rell.test.block– Builder for test blocks containing transactionsrell.test.tx– Builder for test transactions containing operationsrell.test.op– Represents a test operation call (created by calling any operation)
Builders follow a fluent pattern and support method chaining. All three support .run() to execute immediately and
.run_must_fail() to assert failure.
Assertions
The rell.test namespace provides assertion functions for verifying test expectations:
assert_equals– Assert two values are equalassert_not_equals– Assert two values are not equalassert_true,assert_false– Assert boolean valuesassert_null,assert_not_null– Assert nullabilityassert_gt,assert_ge,assert_lt,assert_le– Assert comparisons (greater/less than, or equal)assert_ge_le,assert_ge_lt,assert_gt_le,assert_gt_lt– Assert range boundsassert_fails– Assert a function throws an exception (optionally with a specific message)assert_events– Assert that specific events were emitted in order
Test keys
Predefined test keypairs are available for signing test transactions. These should never be used in production:
rell.test.keypairs– Full keypairs (public and private keys)rell.test.pubkeys– Public keys onlyrell.test.privkeys– Private keys only
Available names: alice, bob, charlie, dave, eve, frank, grace, heidi, trudy.
Block timestamps
Test block timestamps are deterministic and controlled through:
rell.test.last_block_time– Timestamp of the last blockrell.test.next_block_time– Timestamp of the next block to be builtrell.test.block_interval– Time interval between blocks (default: 10 seconds)rell.test.set_next_block_time– Set next block timestamp explicitlyrell.test.set_next_block_time_delta– Set next block timestamp relative to previous blockrell.test.set_block_interval– Change the default block interval
Other utilities
rell.test.nop– Create a no-op operation (useful for making transactions unique)rell.test.get_events– Retrieve all events emitted during the last block construction
For information on writing integration tests using Rell and TypeScript, visit the Chromia learning platform.