Test Wraps In TypeScript
Introduction
In this tutorial we'll learn how to develop custom end to end (e2e) tests for your wrap in TypeScript. These tests will be make calls into your wrap using the JavaScript / TypeScript Polywrap Client.
Prerequisites
You'll need the following NPM packages installed before testing your wrap:
Project Setup
The tests we'll be developing will live next to your wrap's source-code. Polywrap has published pre-configured projects for both wasm/rust
and wasm/assemblyscript
wraps. You can use & reference these when configuring your own project.
In this tutorial we'll assume a fresh wasm/assemblyscript
project has been created via the polywrap create wasm assemblyscript ...
CLI command.
Build The Wrap
Before any tests can be run, we must ensure the wrap has been freshly built. This will ensure we test against the latest version of the wrap's source-code.
When using jest
, we leverage the beforeAll
function to do this. Additionally, the @polywrap/cli-js
package makes it easy to invoke the polywrap build
CLI command to build your wrap.
import { Commands } from "@polywrap/cli-js";
describe("e2e Wrap Tests", () => {
beforeAll(async () => {
const result = await Commands.build({}, {
cwd: `${__dirname}/../../`
});
if (result.exitCode !== 0) {
console.log(result.stdout);
console.error(result.stderr);
fail("Failed to build wrapper.");
}
});
});
This should result in a wrap package being emitted to the build/
directory.
Configure a Polywrap Client
Before we create a new Polywrap client, we must create a configuration for it to use. This can be done through use of the ClientConfigBuilder
. In the case of this example we'll be using the default configuration bundle. If your wrap requires any custom integration dependencies like plugins or environment variables, then now would be the time to configure this.
import {
ClientConfigBuilder,
PolywrapClient
} from "@polywrap/client-js";
describe("e2e Wrap Tests", () => {
const config = new ClientConfigBuilder()
.addDefaults()
.build();
const client = new PolywrapClient(config);
...
});
Load The Wrap
We'll be loading our wrap directly from the build/
directory in the file-system. This can be accomplished in 1 of 2 ways:
- Package Embedding
import { WasmPackage } from "@polywrap/wasm-js";
import fs from "fs";
...
const buildDir = `${__dirname}/../../build`;
const embedPackage = WasmPackage.from(
fs.readFileSync(`${buildDir}/wrap.info`),
fs.readFileSync(`${buildDir}/wrap.wasm`)
);
const uri = "wrap://embed/test-wrapper";
const config = new ClientConfigBuilder()
.addPackage(uri, embedPackage)
.build();
- File-System URIs
const buildDir = `${__dirname}/../../build`;
const uri = `wrap://file/${buildDir}`;
In both cases, you end up with a uri
which can be used to invoke your wrap through the client.
Test Your Wrap
Now that we have a client
invoke wraps, and a uri
to reference our wrap, we're ready to write some tests.
test("Test Case", async () => {
const result = await client.invoke({
uri,
method: "sampleMethod",
args: {
arg: "foo bar baz"
}
});
expect(result.ok).toBe(true);
if (!result.ok) fail(result.error);
expect(result.value).toMatchObject({
result: "foo bar baz"
});
});
Type Safety
In the example above, it shows the use of the client.invoke(...)
In the example above, it shows the use of the
client.invoke(...)` method, which is generic. This means that if your wrap's schema changes, and (for example) the method being called no longer exists or its types change, the test will fail.
In order to help ensure this can be caught sooner, and easier to debug, we suggest generating TypeScript types for your wrap's schema. All you need is an app/typescript
polywrap.yaml
manifest like so:
format: 0.3.0
project:
name: sample-typescript-type-generation
type: app/typescript
source:
schema: ./schema.graphql
And an import schema like so:
#import * into Wrapper from "wrap://file/build"
We suggest putting these files in a folder next to your tests, for example src/__tests__/types/
. Once here, you can generate types by simply running the following command:
polywrap codegen -m ./src/__tests__/types/polywrap.yaml -g ./src/__tests__/types/wrap
With this done, you can now rewrite the test above in a type-safe way.
test("Test Case", async () => {
const result = await Wrapper_Module.sampleMethod({
arg: "foo bar baz"
}, client, uri);
expect(result.ok).toBe(true);
if (!result.ok) fail(result.error);
expect(result.value.result).toBe("foo bar baz");
});
debug, we suggest generating TypeScript types for your wrap's schema. All you need is an app/typescript
polywrap.yaml
manifest like so:
format: 0.3.0
project:
name: sample-typescript-type-generation
type: app/typescript
source:
schema: ./schema.graphql
We suggesting putting this polywrap.yaml
manifest in a folder next to your tests like src/__tests__/types/
. Once here, you can generate types by simply running the following command:
polywrap codegen -m ./src/__tests__/types/polywrap.yaml -g ./src/__tests__/types/wrap
With this done, you can now rewrite the test above in a type-safe way.
import { Wrapper_Module } from "./types/wrap";
...
test("Test Case", async () => {
const result = await Wrapper_Module.sampleMethod({
arg: "foo bar baz"
}, client, uri);
expect(result.ok).toBe(true);
if (!result.ok) fail(result.error);
expect(result.value.result).toBe("foo bar baz");
});
Conclusion
And that's a wrap!
import { Commands } from "@polywrap/cli-js";
import {
ClientConfigBuilder,
PolywrapClient
} from "@polywrap/client-js";
import { Wrapper_Module } from "./types/wrap";
jest.setTimeout(50000);
describe("e2e Wrapper Tests", () => {
const config = new ClientConfigBuilder()
.addDefaults()
.build();
const client = new PolywrapClient(config);
const buildDir = `${__dirname}/../../build`;
const uri = `wrap://file/${buildDir}`;
beforeAll(async () => {
const result = await Commands.build({}, {
cwd: `${__dirname}/../../`
});
if (result.exitCode !== 0) {
console.log(result.stdout);
console.error(result.stderr);
fail("Failed to build wrapper.");
}
});
test("Test Case", async () => {
const result = await Wrapper_Module.sampleMethod({
arg: "foo bar baz"
}, client, uri);
expect(result.ok).toBe(true);
if (!result.ok) fail(result.error);
expect(result.value.result).toBe("foo bar baz");
});
});
If you'd like to see an in-production wrap w/ tests in TypeScript, checkout the ethereum wrap's tests here.