Skip to main content

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:

  1. 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();
  1. 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.