Rell testing guide
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");
For information on writing integration tests using Rell and TypeScript, visit the Chromia learning platform.