Skip to content

wasm-ecosystem/assemblyscript-unittest-framework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Assemblyscript Unittest Framework

A comprehensive AssemblyScript testing solution, offering developers a robust suite of features to ensure their code performs as expected:

  • Function Mocking
  • Coverage statistics
  • Expectations

Getting Started

Install Assemblyscript Unittest Framework using npm

npm install --save-dev assemblyscript-unittest-framework

Let's get started by writing a test for a simple function that add two numbers. Assume that there is already environment of assemblyscript.

First, create source/sum.ts:

export function add(a: i32, b: i32): i32 {
  return a + b;
}

Then, create a file named tests/sum.test.ts. This will contain our actual test:

import { test, expect, endTest } from "assemblyscript-unittest-framework/assembly";
import { add } from "../source/sum";

test("sum", () => {
  expect(add(1, 2)).equal(3);
  expect(add(1, 1)).equal(3);
});
endTest();  // Don't forget it!

Don't forget endTest() at the end of *.test.ts files

Create a config file in project root as-test.config.js:

for cjs:

module.exports = {
  include: ["source", "tests"],
};

for mjs:

export default {
  include: ["source", "tests"],
};

Add the following section to your package.json

{
  "scripts": {
    "test": "as-test"
  }
}

Finally, run npm run test and as-test will print this message:

transform source/sum.ts => build/source/sum.ts.cov
transform build/source/sum.ts.cov => build/source/sum.ts
transform tests/sum.test.ts => build/tests/sum.test.ts
(node:489815) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time

test case: 1/2 (success/total)

Error Message:
        sum:
                tests/sum.test.ts:6:3 (6:3, 6:29)

You can also use npx as-test -h for more information to control detail configurations

Configuration

This is the template of as-test.config.js:

module.exports = {
  // test related code folder
  include: ["source", "tests"],
  exclude: [],

  /** optional: assemblyscript compile flag, default is --exportStart _start -O0 */
  flags: "",

  /**
   * optional: import functions
   * @param {ImportsArgument} runtime
   * @returns
   */
  imports(runtime) {
    return {
      env: {
        logInfo(ptr, len) {
          if (runtime.exports) {
            let arrbuf = runtime.exports.__getArrayBuffer(ptr);
            let str = Buffer.from(arrbuf).toString("utf8");
            console.log(str);
          }
        },
      },
      builtin: {
        getU8FromLinkedMemory(a) {
          return 1;
        },
      },
    };
  },

  /**  optional: template file path, default "coverage" */
  // temp: "coverage",

  /**  optional: report file path, default "coverage" */
  // output: "coverage",

  /** optional: test result output format, default "table" */
  // mode: ["html", "json", "table"],
};

Using Matchers

The simplest way to test a value is with exact equality.

test('two plus two is four', () => {
  expect(2 + 2).equal(4);
});

In this code, expect(2+2) returns an "Value" object. You typically won't do much with these objects except call matchers on them. In this code, .equal(4) is the matcher. When Jest runs, it tracks all the failing matchers so that it can print out nice error messages for you.

Equal

In the most condition, equal is similar as ==, you can use this matcher to compare i32 | i64 | u32 | u64 | f32 | f64 | string just like ==. What's more, it can also be used to compare some inner type, such as Array | Map | Set.

However, Class and Interface cannot be compared directly now.

notEqual is the opposite of equal

Numbers

Most ways of comparing numbers have matcher equivalents, like equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual.

Specially, for float type, use closeTo instead of equal to avoid rounding error.

Nullable

isNull and notNull matcher can be used to a nullable object. Of cource, you can also use equal and notEqual to do same thing with explicit generic declartion expect<T | null>()

Using Mock Function

Because Assemblyscript's grammar is not as flexible as javascript, mock function have a lot of limitation and API design is not similar as jest (a javascript test framework).

However, There is a way to do some mock function.

Imagine that we are testing a function which includes a system-interface:

// source.ts
declare function sys_getTime(): i32;
export function getTime(): bool {
  let errno = sys_getTime();
  if (errno < 0) {
    // error handle
    return false;
  }
  // do something
  return true;
}

To test error handle part, we need to inject some code to sys_getTime and expect to return a errno.

// source.test.ts
test("getTime error handle", () => {
  const fn = mock(sys_getTime, () => {
    return -1;
  });
  expect(getTime()).equal(false); // success
  expect(fn.calls).equal(1);      // success
});
endTest();

mock API can temporary change the behavior of function, effective scope is each test. In this mock function, you can do every thing include expecting arguments, mock return values and so on.

Tips:

  • Because Assemblyscript is a strongly typed language, you should keep the function signature aligned.
  • AssemblyScript does not support closures. If a mock function needs to be called several times in one test, and you want it to return different values or match arguments to different values, using a global counter for this function is a good way to achieve this.

Example for MockFn

  1. expect arguments

    test("check argument", () => {
      const fn = mock(add, (a: i32, b: i32) => {
        expect(a).equal(1);
        return a + b;
      });
      expect(fn.calls).greaterThanOrEqual(1);
    });
  2. return difference value

    const ret = [1,2,3,4,5,6,7,8];  // global variant
    const calls = 0;                // global variant
    test("check argument", () => {
      const fn = mock(add, (a: i32, b: i32) => {
        return ret[calls++];
      });
      expect(fn.calls).greaterThanOrEqual(1);
    });
  3. re-call origin

    test("call origin", () => {
      mock(add, (a: i32, b: i32) => {
        unmock(add);
        const res = add(a,b);
        remock(add);
        return res;
      });
    });

Coverage Report

After testing, you can get a html / json / table format test coverage report include "Statements Coverage", "Branches Coverage", "Functions Coverage", "Lines Coverage"

Architecture

  • assembly/ written in Assemblyscript, provides user-accessible testing APIs such as test, inspect, mock, etc.
  • src/ written in Typescript, implements the test functionality.