Reflection And Testing
Reflect
Import reflect for runtime metadata:
- decorators:
decorators,hasDecorator,decorator - functions:
function,parameters,returnType,doc,docs - classes:
class,fields,methods,staticMethods,parent,interfaces,constructors,typeBindings - interfaces:
interfaceMethods,interfaceParents - modules:
module,exports - method lookup:
method,staticMethod - values:
typeOf
Docblocks use ## line comments or /** ... */ block comments immediately
before the declaration they describe. They are attached to functions, classes,
interfaces, methods, static methods, constructors, and interface method
signatures.
Use reflect.doc(value) to read the raw docblock for a reflected value. It
returns the doc text as a string, or null when the target has no docblock.
Use reflect.docs(value) when tooling needs a structured dictionary:
text, summary, body, and lines.
For methods, first obtain the method handle with reflect.method() or
reflect.staticMethod(). Interface method docs are exposed in the dictionaries
returned by reflect.interfaceMethods().
import io;
import reflect;
## Handles the user index route.
@route("GET", "/users")
func index(): string {
return "users";
}
io.println(reflect.hasDecorator(index, "route"));
io.println(reflect.parameters(index));
io.println(reflect.doc(index));
io.println(reflect.docs(index)["summary"]);
import io;
import reflect;
/**
* User-facing controller.
* Routes are discovered by metadata.
*/
class UserController {
## Lists visible users.
func list(): string {
return "users";
}
}
## Implemented by values with a display name.
interface Named {
## Returns the display name.
func name(): string;
}
io.println(reflect.doc(UserController));
io.println(reflect.doc(reflect.method(UserController, "list")));
io.println(reflect.doc(Named));
io.println(reflect.interfaceMethods(Named)[0]["doc"]);
Frameworks should prefer reflection over custom syntax when discovering routes, middleware, services, and metadata.
Test
Import test for class-based tests. Test cases are ordinary classes that
extend test.Test; methods decorated with @test are discovered by
test.run(). This means test code uses the same class, decorator, module, and
visibility rules as application code.
- class:
Test - function:
run
Optional lifecycle hooks are called when present:
setupClass()once before the selected tests in the classteardownClass()once after the selected tests in the classsetup()before each selected test methodteardown()after each selected test method
Use @tag("name") to group tests and pass {"tags": ["name"]} to test.run
to run only matching methods.
import io;
import test;
class MathTest extends test.Test {
int value = 0;
func setup(): void {
this.value = 2;
}
@tag("fast")
@test
func addition(): void {
this.assertEquals(4, this.value + 2);
this.assertTrue(this.value > 0);
}
@test
func collections(): void {
this.assertContains(["red", "green"], "red");
this.assertContains({"name": "Ada"}, "name");
this.assertNotEmpty(["ok"]);
}
}
let result = test.run(MathTest);
io.println(result["total"]);
io.println(result["passed"]);
io.println(result["failed"]);
let fast = test.run(MathTest, {"tags": ["fast"]});
io.println(fast["total"]);
test.run() returns a dictionary:
total: number of selected test methodspassed: number that completed without throwingfailed: number that threw an assertion or runtime errorfailures: list of failure strings, prefixed with the test method name
Test Assertions
The base test.Test class provides assertion methods. Assertions throw on
failure, and the test runner records the thrown error as a failed test.
| Method | Meaning |
|---|---|
equal(actual, expected) |
Legacy equality assertion; kept for concise tests |
assertEquals(expected, actual) |
Deep equality for primitives, lists, dicts, sets, enum variants, and object fields |
assertEqual(expected, actual) |
Singular alias for assertEquals |
assertNotEquals(expected, actual) |
Fails when values are deeply equal |
assertNotEqual(expected, actual) |
Singular alias for assertNotEquals |
isTrue(value) |
Legacy boolean true assertion |
assertTrue(value) |
Fails unless value is true |
isFalse(value) |
Legacy boolean false assertion |
assertFalse(value) |
Fails unless value is false |
assertNull(value) |
Fails unless value is null |
notNull(value) |
Legacy non-null assertion |
assertNotNull(value) |
Fails when value is null |
assertContains(haystack, needle) |
String substring, bytes subsequence/byte, list item, dict key, or set member |
assertNotContains(haystack, needle) |
Inverse of assertContains |
assertEmpty(value) |
Empty string, bytes, list, dict, set, range, or null |
assertNotEmpty(value) |
Inverse of assertEmpty |
assertGreaterThan(expected, actual) |
Ordered numeric or string comparison |
assertGreaterThanOrEqual(expected, actual) |
Ordered numeric or string comparison |
assertLessThan(expected, actual) |
Ordered numeric or string comparison |
assertLessThanOrEqual(expected, actual) |
Ordered numeric or string comparison |
fail() / fail(message) |
Fails immediately |
Prefer the assert... names in new tests. The shorter legacy names remain
available because older examples and small scripts use them.
import test;
class UserTest extends test.Test {
@test
func profileShape(): void {
let profile = {"name": "Ada", "roles": ["admin", "editor"]};
this.assertEquals("Ada", profile["name"]);
this.assertContains(profile, "roles");
this.assertContains(profile["roles"], "admin");
this.assertNotContains(profile["roles"], "guest");
this.assertGreaterThan(1, profile["roles"].length());
}
}
testing.assertions is a source module with string assertion helpers:
contains(text, needle)startsWith(text, prefix)endsWith(text, suffix)isBlank(text)
These helpers return booleans and are useful in application code or custom
assertions. In tests, combine them with this.assertTrue(...) when you want
the runner to record a failure:
import testing.assertions as assert;
import test;
class MessageTest extends test.Test {
@test
func greeting(): void {
this.assertTrue(assert.contains("hello Ada", "Ada"));
this.assertTrue(assert.startsWith("Geblang", "Geb"));
}
}