Testing
The SDK provides a test framework for writing and running AssemblyScript tests against your JAM service, with configurable ecalli host call mocks.
Architecture
Testing involves two layers that work together:
┌─────────────────────────────────────┐
│ AssemblyScript test code (WASM) │
│ │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Your test │ │ SDK test utils │ │
│ │ assertions│ │ TestGas, etc. │ │
│ └───────────┘ └───────┬────────┘ │
│ │ │
│ @external("ecalli", ...) │
└─────────────────────────┼───────────┘
│ WASM imports
┌─────────────────────────┼───────────┐
│ sdk-ecalli-mocks (Node.js) │
│ │
│ Stub implementations of ecalli │
│ host calls + configuration state │
└─────────────────────────────────────┘
-
sdk-ecalli-mocks/— A TypeScript (Node.js) package that provides stub implementations of all 27 ecalli host calls (general 0-5 + 100, refine 6-13, accumulate 14-26). These stubs satisfy the WASM imports at test time and hold configurable state (gas value, storage map, preimage data, etc.). -
sdk/test/test-ecalli/— AssemblyScript wrapper classes (TestGas,TestFetch,TestLookup,TestStorage,TestEcalli) that bridge to the JS-side stubs via@external("ecalli", ...)WASM imports. These give your AS test code a high-level API for configuring stub behavior.
Writing Tests
Test structure
Tests use the test() helper and Assert class from the SDK:
import { Assert, Test, test } from "@fluffylabs/as-lan/test";
export const TESTS: Test[] = [
test("my feature works", () => {
const assert = Assert.create();
assert.isEqual(1 + 1, 2, "basic math");
return assert;
}),
];
Each test function returns an Assert instance. Use assert.isEqual(actual, expected, msg)
to add assertions — any failure is recorded and reported after the test completes.
Test runner
Each service needs a test-run.ts entry point that registers test suites:
import { TestSuite, runTestSuites } from "@fluffylabs/as-lan/test";
import * as myTests from "./index.test";
export function runAllTests(): void {
runTestSuites([TestSuite.create(myTests.TESTS, "my-service.ts")]);
}
And a bin/test.js that boots the WASM and runs:
import { setMemory } from "ecalli";
import { memory, runAllTests } from "../build/test.js";
setMemory(memory);
runAllTests();
Build and run
npm test # compiles test target and runs bin/test.js
This compiles your test-run entry point to WASM (with the test target from
asconfig.json), then executes it in Node.js with the ecalli stubs providing
host call implementations.
Configuring Ecalli Mocks
By default the stubs provide sensible test values (e.g. gas() returns
1_000_000, lookup() returns "test-preimage", read()/write() use an
in-memory Map). You can override these from within your AS test code.
TestGas
Set the value returned by the gas() ecalli:
import { TestGas } from "@fluffylabs/as-lan/test";
TestGas.set(500); // gas() will now return 500
TestFetch
Set fixed data returned by the fetch() ecalli (overrides the default
kind-dependent pattern):
import { TestFetch } from "@fluffylabs/as-lan/test";
const data = new Uint8Array(4);
data[0] = 0xde; data[1] = 0xad; data[2] = 0xbe; data[3] = 0xef;
TestFetch.setData(data);
TestLookup
Set the preimage returned by the lookup() ecalli:
import { TestLookup } from "@fluffylabs/as-lan/test";
const preimage = new Uint8Array(3);
preimage[0] = 1; preimage[1] = 2; preimage[2] = 3;
TestLookup.setPreimage(preimage);
TestStorage
Pre-populate or delete entries in the read()/write() stub storage:
import { BytesBlob } from "@fluffylabs/as-lan";
import { TestStorage } from "@fluffylabs/as-lan/test";
// Pre-populate a key
const key = BytesBlob.wrap(String.UTF8.encode("counter"));
const value = BytesBlob.wrap(new Uint8Array(8));
TestStorage.set(key, value);
// Delete a key
TestStorage.set(key, null);
TestEcalli
Reset all configuration to defaults and clear storage:
import { TestEcalli } from "@fluffylabs/as-lan/test";
TestEcalli.reset();
Default Stub Behavior
General (0-5, 100):
| Ecalli | Default |
|---|---|
gas() | Returns 1_000_000 |
fetch() | Writes a 16-byte kind-dependent pattern, returns 16 |
lookup() | Writes "test-preimage" (13 bytes), returns 13 |
read() | Reads from in-memory Map; returns NONE (-1) if key missing |
write() | Writes to in-memory Map; returns previous value length or NONE |
info() | Returns a 96-byte structure (code_hash=0xAA..., balance=1000) |
log() | Prints [LEVEL] target: message to console |
Refine (6-13):
| Ecalli | Default |
|---|---|
historical_lookup() | Writes "test-historical" (15 bytes), returns 15 |
export_() | Returns incrementing segment index (0, 1, 2, …) |
machine() | Returns incrementing machine ID (0, 1, 2, …) |
peek() | Returns OK (0) |
poke() | Returns OK (0) |
pages() | Returns OK (0) |
invoke() | Returns HALT (0), writes r8 = 0 |
expunge() | Returns OK (0) |
Accumulate (14-26):
| Ecalli | Default |
|---|---|
bless() | Returns OK (0) |
assign() | Returns OK (0) |
designate() | Returns OK (0) |
checkpoint() | Returns remaining gas (delegates to gas() mock) |
new_service() | Returns incrementing service ID (256, 257, …) |
upgrade() | Returns OK (0) |
transfer() | Returns OK (0) |
eject() | Returns OK (0) |
query() | Returns NONE (-1), writes r8 = 0 |
solicit() | Returns OK (0) |
forget() | Returns OK (0) |
yield_result() | Returns OK (0) |
provide() | Returns OK (0) |
See the fibonacci and ecalli-test examples for usage examples.