Easy way to write tests in Motoko and run them with mops test
.
The library supports Mops Message Format v1.0.
mops add test --dev
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.
import {test} "mo:test";
test("simple test", func() {
assert true;
});
test("test my number", func() {
assert 1 > 0;
});
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;
});
});
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;
});
If there are await
s 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;
});
});
Use testsys
to run tests with system
capability.
import {testsys} "mo:test";
testsys<system>("test", func<system>() {
myFunc<system>();
});
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
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(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(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"
// 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});
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(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(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([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(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
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