Testing

Built-in test support with #[test], assert_eq, 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_eq(actual, expected) to check values and assert(condition) to check boolean conditions.

Basic tests
#[test]
fn test_addition() {
    assert_eq(2 + 2, 4);
}
#[test]
fn test_string_interpolation() {
    let name = "Hew";
    assert_eq(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.

Running tests
# 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. Use await to get responses from receive fn handlers and sleep_ms() to wait for fire-and-forget messages to be processed.

Actor tests
actor Counter {
    let count: Int;
    receive fn increment() {
        count += 1;
    }
    receive fn get_count() -> Int {
        count
    }
}
#[test]
fn test_counter_increment() {
    let c = spawn Counter(count: 0);
    c.increment();
    c.increment();
    c.increment();
    sleep_ms(100);
    let count = await c.get_count();
    assert_eq(count, 3);
}
#[test]
fn test_counter_add() {
    let c = spawn Counter(count: 0);
    c.increment();
    c.increment();
    sleep_ms(100);
    let count = await c.get_count();
    assert_eq(count, 2);
}

Testing actor-to-actor communication

Test multi-actor patterns by spawning the full topology and checking the final state. Workers, collectors, and forwarders can all be exercised in tests.

Fan-in test
actor Collector {
    let total: Int;
    receive fn report(value: Int) {
        total += value;
    }
    receive fn get_total() -> Int {
        total
    }
}
actor Worker {
    let id: Int;
    let collector: Collector;
    receive fn compute(n: Int) {
        collector.report(n * n);
    }
}
#[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);  // 4
    w2.compute(3);  // 9
    w3.compute(4);  // 16
    sleep_ms(200);
    let total = await collector.get_total();
    assert_eq(total, 29);
}

Testing select and join

select and join expressions work naturally in test functions. Test timeout behaviour with after clauses and parallel results with join.

Select and join tests
actor FastActor {
    let value: Int;
    receive fn get() -> Int { value }
}
actor SlowActor {
    let value: Int;
    receive fn get() -> Int {
        sleep_ms(200);
        value
    }
}
#[test]
fn test_select_picks_fast() {
    let fast = spawn FastActor(value: 42);
    let slow = spawn SlowActor(value: 999);
    let result = select {
        x from await fast.get() => x,
        y from await slow.get() => y,
    };
    assert_eq(result, 42);
}
#[test]
fn test_select_timeout() {
    let slow = spawn SlowActor(value: 123);
    let result = select {
        x from await slow.get() => x,
        after 50 => -1,
    };
    assert_eq(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 = join {
        await w1.get(),
        await w2.get(),
        await w3.get(),
    };
    assert_eq(results.0 + results.1 + results.2, 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.

Algorithm tests
fn binary_search(arr: Vec<Int>, target: Int) -> Int {
    var low = 0;
    var high = arr.len();
    while low < high {
        let mid = low + (high - low) / 2;
        let val = arr.get(mid);
        if val == target { return mid; }
        if val < target { low = mid + 1; }
        else { high = mid; }
    }
    return -1;
}
#[test]
fn test_binary_search_found() {
    let arr: Vec<Int> = Vec::new();
    arr.push(1);
    arr.push(3);
    arr.push(5);
    arr.push(7);
    arr.push(9);
    assert_eq(binary_search(arr, 7), 3);
}
#[test]
fn test_binary_search_not_found() {
    let arr: Vec<Int> = Vec::new();
    arr.push(1);
    arr.push(3);
    arr.push(5);
    assert_eq(binary_search(arr, 4), -1);
}
#[test]
fn test_binary_search_empty() {
    let arr: Vec<Int> = Vec::new();
    assert_eq(binary_search(arr, 1), -1);
}

Testing enums and pattern matching

Use assert_eq with match to verify enum behaviour and Option/Result handling.

Enum and Option tests
enum Color { Red; Green; Blue; }
fn color_code(c: Color) -> Int {
    match c {
        Red => 0,
        Green => 1,
        Blue => 2,
    }
}
#[test]
fn test_enum_variants() {
    assert_eq(color_code(Red), 0);
    assert_eq(color_code(Green), 1);
    assert_eq(color_code(Blue), 2);
}
fn safe_divide(a: Int, b: Int) -> Result<Int, String> {
    if b == 0 { Err("division by zero") }
    else { Ok(a / b) }
}
#[test]
fn test_result_ok() {
    let r = safe_divide(10, 2);
    let val = match r {
        Ok(v) => v,
        Err(e) => -1,
    };
    assert_eq(val, 5);
}
#[test]
fn test_result_err() {
    let r = safe_divide(10, 0);
    let is_err = match r {
        Ok(v) => false,
        Err(e) => true,
    };
    assert(is_err);
}