Testing
Built-in test support with #[test] and assert. No external test framework needed.
Writing tests
Annotate any function with #[test] to make it a test case. Test functions take no arguments and return nothing. Use assert(actual == expected) to check values and assert(condition) to check boolean conditions.
#[test]
fn test_addition() {
assert(2 + 2 == 4);
}
#[test]
fn test_string_interpolation() {
let name = "Hew";
assert(f"Hello, {name}!" == "Hello, Hew!");
}
#[test]
fn test_condition() {
let x = 42;
assert(x > 0);
}Running tests
Run all tests in a file with hew test. The test runner discovers every #[test] function, runs them, and reports pass/fail results.
# Run all tests in a file
hew test my_tests.hew
# Run all test files in a directory
hew test tests/Testing actors
Actors are tested the same way as regular code: spawn them, send messages, and assert results. A request-reply call yields Result<R, ActorError<E, C>>, so match the reply. Ordinary calls complete in order, so tests do not need sleeps to wait for earlier messages.
actor Counter {
var count: i64 = 0;
receive fn increment() {
count += 1;
}
receive fn get_count() -> i64 {
count
}
}
#[test]
fn test_counter_increment() {
let c = spawn Counter { count: 0 };
c.increment().expect("actor stopped");
c.increment().expect("actor stopped");
c.increment().expect("actor stopped");
match c.get_count() {
.Ok(count) => assert(count == 3),
.Err(_) => assert(false),
}
}Testing actor-to-actor communication
Test multi-actor patterns by spawning the full topology and checking the final state. Pass one actor's typed handle into another actor's fields to wire them together.
actor Collector {
var total: i64 = 0;
receive fn report(value: i64) {
total += value;
}
receive fn get_total() -> i64 {
total
}
}
actor Worker {
let id: i64;
let collector: Collector;
receive fn compute(n: i64) {
collector.report(n * n).expect("actor stopped");
}
}
#[test]
fn test_fan_in_pattern() {
let collector = spawn Collector { total: 0 };
let w1 = spawn Worker { id: 1, collector: collector };
let w2 = spawn Worker { id: 2, collector: collector };
let w3 = spawn Worker { id: 3, collector: collector };
w1.compute(2).expect("actor stopped"); // 4
w2.compute(3).expect("actor stopped"); // 9
w3.compute(4).expect("actor stopped"); // 16
match collector.get_total() {
.Ok(total) => assert(total == 29),
.Err(_) => assert(false),
}
}Testing select and join
select and await fork (...) expressions work naturally in test functions. A select arm names the actor call directly (no inner await); test timeout behaviour with an after arm and parallel results with await fork (...).
actor FastActor {
let value: i64;
receive fn get() -> i64 { value }
}
actor SlowActor {
let value: i64;
receive fn get() -> i64 {
sleep(200ms);
value
}
}
#[test]
fn test_select_picks_fast() {
let fast = spawn FastActor { value: 42 };
let slow = spawn SlowActor { value: 999 };
let result = select {
x from fast.get() => x.expect("actor stopped"),
y from slow.get() => y.expect("actor stopped"),
};
assert(result == 42);
}
#[test]
fn test_select_timeout() {
let slow = spawn SlowActor { value: 123 };
let result = select {
x from slow.get() => x.expect("actor stopped"),
after 50ms => -1,
};
assert(result == -1);
}
#[test]
fn test_join_waits_for_all() {
let w1 = spawn FastActor { value: 10 };
let w2 = spawn FastActor { value: 20 };
let w3 = spawn FastActor { value: 30 };
let results = await fork (
w1.get(),
w2.get(),
w3.get(),
);
assert(results.0.expect("worker stopped") + results.1.expect("worker stopped") + results.2.expect("worker stopped") == 60);
}Testing pure functions
Pure functions and algorithms are straightforward to test. Define the function alongside its tests in the same file, or import it from another module. Read Vec elements with [i] after the loop's bounds check.
fn binary_search(arr: Vec<i64>, target: i64) -> i64 {
var low = 0;
var high = arr.len();
while low < high {
let mid = low + (high - low) / 2;
let val = arr[mid];
if val == target { return mid; }
if val < target { low = mid + 1; }
else { high = mid; }
}
return -1;
}
#[test]
fn test_binary_search_found() {
var arr: Vec<i64> = Vec.new();
arr.push(1);
arr.push(3);
arr.push(5);
arr.push(7);
arr.push(9);
assert(binary_search(arr, 7) == 3);
}
#[test]
fn test_binary_search_not_found() {
var arr: Vec<i64> = Vec.new();
arr.push(1);
arr.push(3);
arr.push(5);
assert(binary_search(arr, 4) == -1);
}
#[test]
fn test_binary_search_empty() {
var arr: Vec<i64> = Vec.new();
assert(binary_search(arr, 1) == -1);
}Testing enums and pattern matching
Use assert with match to verify enum behaviour and Option/Result handling. Enum variants are ;-separated.
enum Color { Red; Green; Blue; }
fn color_code(c: Color) -> i64 {
match c {
.Red => 0,
.Green => 1,
.Blue => 2,
}
}
#[test]
fn test_enum_variants() {
assert(color_code(.Red) == 0);
assert(color_code(.Green) == 1);
assert(color_code(.Blue) == 2);
}
fn safe_divide(a: i64, b: i64) -> Result<i64, string> {
if b == 0 { Result.Err("division by zero") }
else { Result.Ok(a / b) }
}
#[test]
fn test_result_ok() {
let val = match safe_divide(10, 2) {
.Ok(v) => v,
.Err(e) => -1,
};
assert(val == 5);
}
#[test]
fn test_result_err() {
let is_err = match safe_divide(10, 0) {
.Ok(v) => false,
.Err(e) => true,
};
assert(is_err);
}