ZenVoich / test

Motoko testing library to run tests with mops
MIT License
5 stars 3 forks source link

Motoko Testing

Easy way to write tests in Motoko and run them with mops test.

The library supports Mops Message Format v1.0.

Features

Install

mops add test --dev

Usage

Put your tests in test directory in *.test.mo files.

Use test and suite functions in conjunction with assert expression.

Run mops test to run tests.

Simple test

import {test} "mo:test";

test("simple test", func() {
    assert true;
});

test("test my number", func() {
    assert 1 > 0;
});

Test suites

Use suite to group your tests.

import {test; suite} "mo:test";

suite("my test suite", func() {
    test("simple test", func() {
        assert true;
    });

    test("test my number", func() {
        assert 1 > 0;
    });
});

Skip test

Use skip to skip tests.

import {test; skip} "mo:test";

skip("this test will never run", func() {
    assert false;
});

test("this test will run", func() {
    assert true;
});

Async tests

If there are awaits in your tests, use functions from mo:test/async.

import {test; suite} "mo:test/async";

await suite("my async test suite", func() : async () {
    await test("async test", func() : async () {
        let res = await myAsyncFn();
        assert Result.isOk(res);
    });

    test("should generate unique values", func() : async () {
        let a = await generate();
        let b = await generate();
        assert a != b;
    });
});

Tests with <system> capability

Use testsys to run tests with system capability.

import {testsys} "mo:test";

testsys<system>("test", func<system>() {
    myFunc<system>();
});

Expect

import {test; expect} "mo:test";

Expect consists of a number of "matchers" that let you validate different things.

Compared to assert, in case of fail expect shows you the details of the values.

For example assert:

assert myNat == 1;
// execution error, assertion failure

We only know that myNat is not equal to 1, but what is the actual value of myNat? To know this, we have to add a new line Debug.print(debug_show(myNat)).

but with expect:

expect.nat(myNat).equal(1);
// execution error, explicit trap:
// ! received 22
// ! expected 1

here we see the actual value of myNat

expect.nat (nat8, nat16, nat32, nat64, int, int8, int16, int32, int64)

import {test; expect} "mo:test";

expect.nat(x).equal(10); // x == 10
expect.nat(x).notEqual(10); // x != 10
expect.nat(x).less(10); // x < 10
expect.nat(x).lessOrEqual(10); // x <= 10
expect.nat(x).greater(10); // x > 10
expect.nat(x).greaterOrEqual(10); // x >= 10

expect.int(x).equal(10); // x == 10 (Int)
expect.int64(x).equal(10); // x == 10 (Int64)
expect.nat32(x).equal(10); // x == 10 (Nat32)

expect.char

expect.char(c).equal('a'); // c == 'a'
expect.char(c).notEqual('a'); // c != 'a'
expect.char(c).less('a'); // c < 'a'
expect.char(c).lessOrEqual('a'); // c <= 'a'
expect.char(c).greater('a'); // c > 'a'
expect.char(c).greaterOrEqual('a'); // c >= 'a'

expect.text

expect.text(foo).equal("bar"); // foo == "bar"
expect.text(foo).notEqual("bar"); // foo != "bar"
expect.text(foo).contains("bar"); // Text.contains(foo, #text("bar"))
expect.text(foo).startsWith("bar"); // Text.startsWith(foo, #text("bar"))
expect.text(foo).endsWith("bar"); // Text.endsWith(foo, #text("bar"))"

expect.text(foo).less("bar"); // foo < "bar"
expect.text(foo).lessOrEqual("bar"); // foo <= "bar"
expect.text(foo).greater("bar"); // foo > "bar"
expect.text(foo).greaterOrEqual("bar"); // foo >= "bar"

expect.option

// optional Nat
let optNat = ?10;
expect.option(optNat, Nat.toText, Nat.equal).equal(?10); // optNat == ?10
expect.option(optNat, Nat.toText, Nat.equal).notEqual(?25); // optNat != ?25
expect.option(optNat, Nat.toText, Nat.equal).isNull(); // optNat == null

// optional custom type
type MyType = {
    x : Nat;
    y : Nat;
};

func showMyType(a : MyType) : Text {
    debug_show(a);
};

func equalMyType(a : MyType, b : MyType) : Bool {
    a.x == b.x and a.y == b.y
};

let val = ?{x = 1; y = 2};

expect.option(val, showMyType, equalMyType).notEqual(null);
expect.option(val, showMyType, equalMyType).isSome(); // != null
expect.option(val, showMyType, equalMyType).equal(?{x = 1; y = 2});

expect.result

type MyRes = Result.Result<Nat, Text>;

func show(a) = debug_show(a);
func equal(a, b) = a == b

let ok : MyRes = #ok(22);
let err : MyRes = #err("error");

expect.result<Nat, Text>(ok, show, equal).isOk();
expect.result<Nat, Text>(ok, show, equal).equal(#ok(22));

expect.result<Nat, Text>(err, show, equal).isErr();
expect.result<Nat, Text>(err, show, equal).equal(#err("error"));
expect.result<Nat, Text>(err, show, equal).equal(#err("other error"));

expect.principal

expect.principal(id).isAnonymous(); // Principal.isAnonymous(id)
expect.principal(id).notAnonymous(); // not Principal.isAnonymous(id)

expect.principal(id).equal(id2); // id == id2
expect.principal(id).notEqual(id2); // id != id2

expect.principal(id).less(id2); // id < id2
expect.principal(id).lessOrEqual(id2); // id <= id2
expect.principal(id).greater(id2); // id > id2
expect.principal(id).greaterOrEqual(id2); // id >= id2

expect.bool

expect.bool(x).isTrue(); // a == true
expect.bool(x).isFalse(); // a == false
expect.bool(x).equal(b); // a == b
expect.bool(x).notEqual(b); // a != b

expect.array

expect.array([1,2,3], Nat.toText, Nat.equal).equal([1,2,3]);
expect.array([1,2,3], Nat.toText, Nat.equal).notEqual([1,2]);

expect.array([1,2,3], Nat.toText, Nat.equal).contains(3); // array contains element 3
expect.array([1,2,3], Nat.toText, Nat.equal).notContains(10); // array does not contain element 10

expect.array([1,2,3,4], Nat.toText, Nat.equal).size(4);

expect.blob

expect.blob(blob).size(4); // blob.size() == 4
expect.blob(blob).equal(blob2); // blob == blob2
expect.blob(blob).notEqual(blob2); // blob != blob2

expect.blob(blob).less(blob2); // blob < blob2
expect.blob(blob).lessOrEqual(blob2); // blob <= blob2
expect.blob(blob).greater(blob2); // blob > blob2
expect.blob(blob).greaterOrEqual(blob2); // blob >= blob2

expect.call

Does not catch traps.

func myFunc() : async () {
    throw Error.reject("error");
};

func noop() : async () {
    // do not throw an error
};

await expect.call(myFunc).reject(); // ok
await expect.call(noop).reject(); // fail