{"id":22862384,"url":"https://github.com/dylibso/xtp-test-js","last_synced_at":"2026-02-23T14:04:09.198Z","repository":{"id":233329989,"uuid":"785357065","full_name":"dylibso/xtp-test-js","owner":"dylibso","description":"A JavaScript/TypeScript test framework for xtp / Extism plugins.","archived":false,"fork":false,"pushed_at":"2025-02-11T20:34:13.000Z","size":3220,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-30T22:02:10.412Z","etag":null,"topics":["extism","plugins","testing","unit-testing","wasm","webassembly"],"latest_commit_sha":null,"homepage":"https://extism.org/docs/concepts/testing/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dylibso.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-04-11T18:16:34.000Z","updated_at":"2025-02-26T05:46:25.000Z","dependencies_parsed_at":"2024-04-15T20:28:37.620Z","dependency_job_id":"f5841b65-cb81-405f-b985-2900cfeebfdf","html_url":"https://github.com/dylibso/xtp-test-js","commit_stats":null,"previous_names":["dylibso/xtp-test-js"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/dylibso/xtp-test-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-test-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-test-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-test-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-test-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dylibso","download_url":"https://codeload.github.com/dylibso/xtp-test-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dylibso%2Fxtp-test-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29745114,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T07:44:07.782Z","status":"ssl_error","status_checked_at":"2026-02-23T07:44:07.432Z","response_time":90,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extism","plugins","testing","unit-testing","wasm","webassembly"],"created_at":"2024-12-13T10:13:21.175Z","updated_at":"2026-02-23T14:04:09.182Z","avatar_url":"https://github.com/dylibso.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xtp-test\n\nA JavaScript test framework for [xtp](https://getxtp.com) /\n[Extism](https://extism.org) plugins.\n\n## Example\n\n```javascript\n// test.js\nimport { Test } from \"@dylibso/xtp-test\";\n\nexport function test() {\n  // call a function from some Extism plugin (you'll link these up in the CLI command to run the test),\n  // passing in some data and getting back `MemoryView`, which we convert to JSON using the `MemoryView.json`\n  // method\n  const res = Test.call(\"count_vowels\", \"some input\").json();\n  const count = res[\"count\"];\n  // assert the count of the vowels is correct, giving the test case a name (which will be shown in the CLI output)\n  Test.assertEqual(\"count_vowels of 'some input'\", count, 4);\n\n  // create a group of tests, which will be run together. This resets the plugin before and after the group is complete.\n  Test.group(\"count_vowels maintains state\", () =\u003e {\n    let accumTotal = 0;\n    const expectedFinalTotal = 12;\n    for (let i = 0; i \u003c 3; i++) {\n      const output = Test.call(\"count_vowels\", \"this is a test\").json();\n      accumTotal += output.count;\n      Test.assertEqual(\n        `total count increased to: ${accumTotal}`,\n        accumTotal,\n        4 * (i + 1),\n      );\n    }\n\n    Test.assertEqual(\n      \"expected total reached by end of test\",\n      accumTotal,\n      expectedFinalTotal,\n    );\n  });\n\n  // this function is also an Extism plugin, so return an int32 value (non-zero returns will cause the whole test suite to fail.)\n  return 0;\n}\n```\n\n## API Docs\n\n`Test` is the primary entrypoint to this library. It exposes plugin calling\nfunctions and timing \u0026 assetion functions to validate expectations of plugin\nbehavior.\n\n```ts\nexport class Test {\n  // call a function from the Extism plugin being tested, passing in `Input` and returning the output as `MemoryView`, which \n  // can be used to convert the type to a JavaScript native value.\n  static call(funcName: string, input: Input): MemoryView { ... }\n\n  // read the mock test input provided by the test runner, returns `MemoryView`.\n  // this input is defined in an xtp.toml file, or by the --mock-input-data or --mock-input-file flags.\n  static mockInput(): MemoryView { ... }\n  \n  // Run a test group, resetting the plugin before and after the group is run.\n  static group(name: string, callback: () =\u003e void) { .. }\n\n  // Reset the loaded plugin, clearing all state.\n  static reset() { ... }\n\n  // call a function from the Extism plugin being tested, passing in `Input` and get the number of nanoseconds spent in the function.\n  static timeNanoseconds(funcName: string, input: Input): number { ... }\n\n  // call a function from the Extism plugin being tested, passing in `Input` and get the number of seconds spent in the function.\n  static timeSeconds(funcName: string, input: Input): number { ... }\n\n  // assert that the `outcome` is true, naming the assertion with `name`, which will be used as a label in the CLI runner. The `reason` argument\n  // will be used to print a message when the assertion fails, this should contain some additional information about values being compared.\n  static assert(name: string, outcome: boolean, reason: string) { ... }\n\n  // assert that `x` and `y` are equal, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertEqual(msg: string, x: unknown, y: unknown) { ... }\n  \n  // assert that `x` and `y` are not equal, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertNotEqual(msg: string, x: unknown, y: unknown) { ... }\n\n  // assert that `x` is greater than `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertGreaterThan(msg: string, x: any, y: any) { ... }\n\n  // assert that `x` is greater than or equal to `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertGreaterThanOrEqualTo(msg: string, x: any, y: any) { ... }\n  \n  // assert that `x` is less than `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertLessThan(msg: string, x: any, y: any) { ... }\n  \n  // assert that `x` is less than or equal to `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.\n  static assertLessThanOrEqualTo(msg: string, x: any, y: any) { ... }\n}\n```\n\n`Input` is a type to represent various kinds of function call input data.\n\n```ts\ntype Input = string | ArrayBuffer | object | undefined;\n```\n\n`MemoryView` wraps an Extism memory handle, allowing you to convert between multiple types without dealing directly\nwith low-level memory access.\n\n```ts\n// Provides access to data in Extism memory\nexport class MemoryView extends DataView {\n  ...\n\n  // Returns true if the underlying memory handle is empty or undefined.\n  isEmpty(): boolean {...}\n\n  // Get the JSON representation of a value stored in Extism memory\n  json(): any { ... }\n\n  // Get the string representation of a value stored in Extism memory\n  text(): string { ... }\n\n  // Read bytes from Extism memory into an ArrayBuffer\n  arrayBuffer(): ArrayBuffer { ... }\n}\n```\n\n## Usage\n\nFollow the steps to compile this to WebAssembly using the\n[Extism `js-pdk` toolchain](https://github.com/extism/js-pdk), in particular,\nthe instructions to\n[**use a bundler**](https://github.com/extism/js-pdk?tab=readme-ov-file#using-with-a-bundler).\n\nQuick steps:\n\n**1. Install `extism-js` compiler:**\n\n```sh\ncurl -O https://raw.githubusercontent.com/extism/js-pdk/main/install.sh\nsh install.sh\n```\n\n**2. Create your test script:**\n\nYou need an interface file to link the `xtp-list` library, so create `test.d.ts`\nand paste this:\n\n```ts\n// test.d.ts\ndeclare module \"main\" {\n  export function test(): I32;\n}\n\ndeclare module \"xtp:test\" {\n  interface harness {\n    assert(name: PTR, value: I64, reason: PTR): void;\n    call(func: PTR, input: PTR): PTR;\n    time(func: PTR, input: PTR): I64;\n    group(name: PTR): void;\n    reset(): void;\n    mock_input(): PTR;\n  }\n}\n```\n\nYour test will call function exports, but here we demonstrate calling our\n`count_vowels` function from an example module:\n\n```javascript\n// test.js\nimport { Test } from \"@dylibso/xtp-test\";\n\nexport function test() {\n  const res = Test.call(\"count_vowels\", \"some input\").json();\n  const count = res[\"count\"];\n  Test.assertEqual(\"count_vowels of 'some input'\", count, 4);\n  return 0;\n}\n```\n\n**3. Compile your test and interface to .wasm:**\n\nOnce you bundle `test.js` using `esbuild` or something similar, you can compile\nyour test to .wasm using `extism-js`:\n\n```sh\nextism-js dist/test.js -i test.d.ts -o test.wasm\n```\n\n**4. Run the test against your plugin:** Once you have your test code as a\n`.wasm` module, you can run the test against your plugin using the `xtp` CLI:\n\n### Install `xtp`\n\n```sh\ncurl https://static.dylibso.com/cli/install.sh | sudo sh\n```\n\n### Run the test suite\n\n```sh\nxtp plugin test ./plugin-*.wasm --with test.wasm --mock-host host.wasm\n#               ^^^^^^^^^^^^^^^        ^^^^^^^^^             ^^^^^^^^^\n#               your plugin(s)         test to run           optional mock host functions\n```\n\n**Note:** The optional mock host functions must be implemented as Extism\nplugins, whose exported functions match the host function signature imported by\nthe plugins being tested.\n\n## Need Help?\n\nPlease reach out via the\n[`#xtp` channel on Discord](https://discord.com/channels/1011124058408112148/1220464672784908358).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylibso%2Fxtp-test-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdylibso%2Fxtp-test-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdylibso%2Fxtp-test-js/lists"}