An assertion library with an "expect" style interface, inspired by Chai's and built for Deno. This is not a one-for-one clone of Chai; rather it is the subset that the authors find most useful with semantics the authors find most intuitive.
It wraps the value under test to provide a collection of properties and methods for chaining assertions.
import { expect } from "https://deno.land/x/[email protected]/mod/index.ts";
Deno.test(() => {
    expect(42).to.equal(42);
    expect({foo: "foo value"}).to.deep.equal({foo: "foo value"});
});- INSTALLING
- USING
- EXTENDING
Install like most Deno dependencies, by importing the module(s).
import { expect } from "https://deno.land/x/[email protected]/mod/index.ts";Although NOT RECOMMENDED, it can be imported unversioned.
import { expect } from "https://deno.land/x/expecto/mod.index.ts";There are a handful of entrypoints:
- mod/index.ts(std) — This is the standard setup; exports- expectand- use, as well as the- AssertionErrorclass in use. Without any calls to- use(), the Expecto returned by- expect()is initialized with the- core,- typing,- membership, and- promisedassertions.
- mod/mocked.ts(mock) — This exports the (default)- mockedassertion mixin that can be applied via- use. It also exports- mockwhich is the std/testing/mock implementation it depends on. NOTE that this requires- mod/index.ts.
In addition, the following are useful to extend Expecto:
- mod/mixin.ts— exports helper types and utilities for creating custom mixins.
Assertions are made by calling expect() with the value under test (actual) then chaining properties for assertion checks.
Additional checks and properties can be made available with use().
use(mocked);
use(customMixin);
Deno.test(() => {
    const spied = mocked.spy(nestedFunc);
    const result = topFunc();
    expect(result).to.customCheck();
    expect(spied).to.be.called(1).and.calledWith(["foo", "bar"]);
});The following predicates only return the current instance of Expecto; they assist with the readility of assertions.
- a/- an
- also
- and
- be
- been
- does
- has/- have
- is
- of
- that
- to
- which
- with
The following "flag" properties are used to modify the assertion that follows them. There are two built-in flags, and mixins can provide others.
Some important notes:
- Not all modifiers are supported by all assertions
- Once an assertion is processed in a chain, all previously-set flags are cleared
Modifies the succeeding check to perform a deep check instead of a strict or shallow check.
expect({foo: "foo value"}).to.deep.equal({foo: "foo value"});Multiple instances of deep before a check behave as if it were only one specified.
Modifies the succeeding check to be negated.
expect(() => { doStuff() }).to.not.throw();As with English, two nots before an assertion cancel each other out:
expect(() => { throw new Error() }).to.not.not.throw(); // NOT RECOMMENDED!Compares that actual strictly equals (===) the given value.
expect(42).to.equal(42);
expect(someObj).to.equal(anotherObj);NOTE that equal() and equals() have identical behavior; use whichever makes the most grammatical sense.
expect(42).to.equal(42);
expect(someObject).which.equals(anotherObj);If deep is applied beforehand, then a comprehensive equality check is performed instead.
expect(someObj).to.deep.equal({foo: "foo value"});If not is applied beforehand, then the check is negated (strictly or deeply).
expect(someObj).to.not.equal(anotherObj);
expect(someObj).to.not.deep.equal({bar: "far value"});A custom message can be provided, which will be used if the check fails.
expect(someObj).to.equal(anotherObj, "objects aren't the same");
expect(someObj).to.not.equal(diffObj, "shouldn't match, but do");Checks that actual throws an error when invoked:
expect(() => throw new Error("oops")).to.throw();A class derived from Error can be provided as the first argument, to check if the thrown error is an instance of that class.
expect(() => throw new TypeError("bad type")).to.throw(TypeError);If the check succeeds, the returned Expecto has the thrown error instance as its actual, so that further checks can be made on the error.
expect(() => throw new Error("oops")).to.throw().with.property("message").to.have.substring("oops");A custom message can be provided as the last argument, which is used if the check fails.
expect(() => throw new TypeError("oops")).to.throw(RangeError, "oops");If not is applied beforehand, the check is negated (and actual is unchanged).
expect(() => {}).to.not.throw();This means, if an error type is provided, the check can succeed if actual throws a different error type.
expect(() => throw new TypeError("oops")).to.not.throw(RangeError); // NOT RECOMMENDEDIf actual is not a function, a TypeError is thrown instead of an AssertionError.  This occurs regardless if not is applied.
expect(42).to.throw();      // throws TypeError
expect(42).to.not.throw();  // still throws TypeErrorChecks that actual exists: is not null nor undefined.
expect(someValue).to.exist();NOTE that equal() and equals() have identical behavior; use whichever makes the most grammatical sense.
expect(someValue).to.exist();
expect(somevalue).exists();A custom message can be provied, which is used if the check fails.
expect(null).to.exist("does not exist!");If not is applied beforehand, it negates the check.
expect(null).to.not.exist();
expect(undefined).to.not.exist();Checks that actual is undefined.
expect(someValue).to.be.undefined();A custom message can be provided, which is used if the check fails.
expect("something").to.be.undefined("is actually defined!");If not is applied beforehand, it negates the check.
expect(42).to.not.be.undefined();
expect(null).to.no.be.undefined();Checks that actual is null.
expect(someValue).to.be.null();A custom message can be provided, which is used if the check fails.
expect("some value").to.be.null("is not null");If not is applied beforehand, it negates the check.
expect(42).to.not.be.null();
expect(undefined).to.not.be.null();Checks that actual is the boolean true.
expect(someValue).to.be.true();It is not enough to be truthy, only true will pass.
expect(true).to.be.true();            // SUCCEEDS
expect("some value").to.be.true();    // FAILSA custom message can be provided, which is used if the check fails.
expect(false).to.be.true("isn't true");
expect("some value").to.be.true("isn't true, either");If not is applied beforehand, it negates the check.
expect(false).to.not.be.true();
expect("some value").to.not.be.true();Checks that actual is the boolean false.
expect(someValue).to.be.false();If it not enough to be falsy, only false will pass.
expect(false).to.be.false();  // SUCCEEDS
expect("").to.be.false();     // FAILS
expect(null).to.be.false();   // FAILSA custom message can be provided, which is used if the check fails.
expect(true).to.be.false("isn't false");
expect(undefined).to.be.false("isn't false, either");If not is applied beforeuand, it negates the check.
expect(true).to.not.be.false;
expect("").to.not.be.false;Checks that actual is a number and is NaN.  This check is necessary since NaN !== NaN in Javascript/Typescript!
expect(someNumber).is.NaN();  // SUCCEEDS if someNumber is NaN
expect(NaN).to.equal(NaN);    // ALWAYS FAILS!The check fails (throws AssertionError) if actual is not a number.
expect("some string").is.NaN();   // FAILS with AssertionErrorA custom message can be provided, which is used if the check fails.
expect(42).to.be.NaN("is not not-a-number");If not is applied beforehand, it negates the check.
expect(42).is.not.NaN();Checks that actual is of the given type, where typing is one of:
- bigint
- boolean
- number
- object
- string
expect(someValue).is.a.typeOf("string");A custom message can be provided, which is used if the check fails.
expect(42).is.a.typeOf("string", "not a string!");If not is applied beforehand, it negates the check.
expect(42).to.not.be.a.typeOf("string");Checks that actual is an instance of the given class.
expect(someValue).is.an.instanceOf(SomeClass);A custom message can be provided, which will be used if the check fails.
expect(new Date()).is.an.instanceOf(RegExp, "not a Regexp!");If not is applied beforehand, it negates the check.
expect(new Date()).to.not.be.an.instanceOf(RegExp);Checks that actual is empty.
expect(someArray).is.empty();
expect(someStr).is.empty();The specific behavior depends on what type actual is:
- Set/- Map— checks- .sizeis 0
- Array/Typed Array (e.g.,- Uint8Array) — checks- .lengthis 0
- ArrayBuffer— checks- .byteLengthis 0
- string— checks- .lengthis 0
- object— checks that it has no properties
A custom message can be provided, which will be used if the check fails.
expect(["foo", "bar"]).is.empty("has stuff!");If not is applied beforehand, it negates the check.
expect(["foo", "bar"]).is.not.empty();If actual does not meet one of the above criteria, a TypeError is thrown instead of AssertionError.  This occurs regardless if not is applied.
expect(42).is.empty();      // throws TypeError
expect(42).is.not.empty();  // still throws TypeErrorModifies the succeeding check to only require one of the members to be present, on a membership check.
expect(["foo", "bar"]).to.have.any.members(["foo", "baz", "flag"]);This flag cancels the all flag.
Modifies the succeeding check to require all of the members to be present, on a membership check.
expect(["foo", "bar"]).to.have.all.memebers(["foo", "bar"]);Note that, for members(), this is its default behavior; it has no effect other than readability.
This flag cancels the any flag.
Checks that actual is in possession of all of the given members.  The exact behavior depends on the type of actual:
- 
Map<K, V>: checks that all of the given members are keys onactualconst someValue = new Map(); someValue.set("foo", "foo value"); someValue.set("bar", "bar value"); .... expect(someValue).to.have.members(["foo", "bar"]) 
- 
Set<V>: checks that all of the given members are contained in Setactualconst someValue = new Set(); someValue.add("foo"); someValue.add("bar"); .... expect(someValue).to.have.members(["foo", "bar"]); 
- 
Array<V>: checks that all of the given members are elements in the arrayactualconst someValue = ["foo", "bar"]; .... expect(someValue).to.have.members(["foo", "bar"]); 
- 
Object: checks that all of the given members are properties onactualconst someValue = { foo: "foo value", bar: "bar balue", } .... expect(someValue).to.have.members(["foo", "bar"]); 
If any is applied beforehand, it checks that any one of the values is present.
const someValue = ["foo", "bar"];
....
expect(someValue).to.have.any.members(["foo", "baz"]);  // SUCCEEDS
expect(someValue).to.have.any.members(["baz", "flag"]); // FAILSBy default a strict comparison is used. If deep is applied beforehand, a comprehensive equality comparison is used.
const someValue = new Set([
    { foo: "foo value" },
    { bar: "bar value" },
]);
....
expect(someValue).to.have.deep.members([
    { foo: "foo value" },
    { bar: "bar value" },
]);If not is applied beforehand, the check is negated.
expect({
    foo: "foo value",
    bar: "bar value",
}).to.not.have.members([ "car", "par" ]);Modifies the succeeding check to expect actual to own the property.
expect({foo: "foo value"}).to.have.own.property("foo");Checks that actual is an object which has the given property.
expect(someValue).to.have.property("foo");If own is applied beforehand, the check only succeeds if actual has the property directly and not from its prototype chain.
expect(someValue).to.have.own.property("foo");If the check succeeds, the returned Expecto has the property's value as its actual, so that further checks can be made on the property.
expect(someValue).to.have.property("foo").to.be.a.typeOf("string").which.equals("foo value")A custom message can be provided, which will be used if the check fails.
expect({foo: "foo value"}).to.have.property("bar", "no bar!!");If not is applied beforehand, it negates the check (and actual is unchanged).
expect({foo: "foo value"}).to.not.have.property("bar");Checks that actual is a string that contains the given substring.
expect(someStr).to.have.substring("a string");A custom message can be provided, which will be used if the check fails.
expect("some string value").to.have.substring("this value", "substring missing");If not is applied beforehand, it negates the check.
expect("some string value").to.not.have.substring("this value");If actual is not a string, a TypeError is thrown instead of AssertionError.  This occurs regardless if not is applied.
expect(42).to.have.substring("42");         // throws TypeError
expect(42).to.not.have.substring("foo");    // still throws TypeErrorChecks that actual is a string that starts with the given substring.
expect(someStr).startsWith("LOG:");A custom message can be provided, which will be used if the check fails.
expect("some string value").startsWith("LOG:", "not the prefix");If not is applied beforehand, it negates the check.
expect("some string value").to.not.startsWith("LOG:");If actual is not a string, a TypeError is thrown instead of AssertionError.  This occurs regardless if not is applied.
expect(42).startsWith("4");         // throws TypeError
expect(42).to.not.startsWith("2");  // sill throws TypeErrorChecks that actual is a string that ends with the given substring.
expect(someSt).endsWith("suffix");A custom message can be provided, which will be used if the check fails.
expect("some string value").endsWith("suffix", "missing suffix");If not is applied beforehand, it negates the check.
expect("some string value").to.not.endsWith("suffix");If actual is not a string, a TypeError is thrown instead of AssertionError.  This occurs regardless if not is applied.
expect(42).endsWith("2");           // throws TypeError
expect(42).to.not.endsWith("4");    // still throws TypeErrorTreats actual as a promise and defers all modifiers and checks until that promise is fulfilled.
The returned Expecto is essentially a thenable Proxy; all the predicates, flags, and checks applied after eventually are resolved when the Execpto is resolved (e.g., by awaiting).
await expect(somePromise).to.eventually.be.a.typeOf("string").which.equal("fulfilled!");The ordering of the chain is maintained, just deferred.
Checks that actual is a promise that is rejected, asynchronously.  Like .eventually, the Expecto returned by this check is a thenable Proxy.  The check will actually be performed once the promise is fulfilled (e.g., by awaiting).
await expect(somePromsie).to.be.rejected();A custom message can be provided, which is used if the check fails.
await expect(Promise.reolve(42)).to.be.rejected("was not rejected");If the check succeeds, the returned Expecto has the rejection reason as its actual, so that further checks can be made on the error.
const somePromise = Promise.reject(new Error("oops!"));
....
await expect(somePromise).to.be.rejected().with.property("message").that.has.substring("oops!");If not is applied beforehand, it negates the check; actual is changed the resolved value.
const somePromise = Promise.resolve("some string");
....
await expect(somePromise).to.not.be.rejected().and.is.typeOf("string");Checks that actual is a promise that is rejected, asynchronously.  Like .eventually, the Expecto returned by this check is a thenable Proxy.  The check will actually be performed once the promise is fulfilled (e.g., by awaiting).
await expect(somePromise).to.be.rejectedWith();If the check succeeds, the returned Expecto has the rejection reason as its actual, so that further checks can be made on the error.
await expect(somePromise).to.be.rejectedWith().with.property("message").that.has.substring("oops");A class can be provided as the first argument, to check if the thrown errorr is an instance of that class.
await expect(() => Promise.reject(new TypeError("wrong type"))).to.be.rejectedWith(TypeError);A custom message can be provided as the last argument, which is used if the check fails.
await expect(() => Promise.reject(new RangeError("out of bounds"))).to.be.rejectedWith(TypeError, "not a type error!");If not is applied beforehand, it negates the check.
await expect(Promise.resolve("some string")).to.not.be.rejectedWith();Note this means the check succeeds if the promise successfully resolved or was rejected with a different error!
Checks that actual is a Spy or Stub and was called.
expect(someSpy).to.have.been.called();A count can be provided in the first argument, that checks the spy was called that number of times.
someSpy();
someSpy();
....
expect(someSpy).to.have.been.called(2);A custom message can be provided as the last argument, which is used if the check fails.
expect(someSpy).to.have.been.called(undefined, "spy never called");If not is applied beforehand, it negates the check.
expect(someSpy).to.have.not.been.called();NOTE the negated check can succeed if a count is provided to called() and the spy is called a different number of times (e.g., called 5 times but checking for .called(3))!
If actual is not a Spy or Stub, a TypeError is thrown istead of an AssertionError.  This occurs regardless if not is applied.
expect(42).to.have.been.called();                   // throws TypeError
expect("some string").to.have.not.been.called();    // still throws TypeErrorChecks that actual is a Spy or Stub that was called with the given arguments.
expect(someSpy).to.have.been.calledWith(["foo", "bar"]);If actual was called multiple times, this check succeeds if at least one of those calls included the given arguments.  By default this check performs a strict (===) equality check over the arguments.
If deep is applied beforehand, a comprehensive equality check of the arguments is performed.
someSpy({
    "foo": "foo value",
    "bar": "bar value",
});
expect(someSpy).to.have.been.calledWith([ {"foo": "foo value", "bar": "bar value" }]);      // fails
expect(someSpy).to.have.been.deep.calledWith([ {"foo": "foo value", "bar": "bar value" }]); // succeedsIf not is applied beforehand, it negates the check.
expect(someSpy).to.not.have.been.calledWith(["foo", "bar"]);If actual is not a Spy or Stub, a TypeError is thrown istead of an AssertionError.  This occurs regardless if not is applied.
expect(42).to.have.been.calledWith([]);                 // throws TypeError
expect("some string").to.have.not.been.calledWith([]);  // still throws TypeErrorExpecto can be extended with additional checks and/or flags using the mixin pattern.
Get started by importing mod/mixin.ts module to access the symbols and utilities for developing your own mixins.
To add a custom mixin to Expecto, implement a factory function that takes the current Expecto-derived class and returns a new Expecto-derived class.
import type { CheckDetails, ExpectoConstructor } from "https://deno.land/x/expecto/mod/mixin.ts";
import { meetsExpectations } from "./custom.ts";
export function customMixin<TargetType, BaseType extends ExpectoConstructor<TargetType>>(Base: BaseType) {
    return class CustomMixin extends Base {
        constructor(...args: any[]) {
            super(...args);
        }
        cusomCheck(): this {
            let result = meetsExpectations(this.actual);
            this.check(result, {
                positiveOp: "does not meet expectations",
                negativeOp: "meets expectations",
            } as CheckDetails)
            return this;
        }
    };
}
### Performing Checks
The assertion check is performed using `.check(result: boolean, details: CheckDetails)`; `result` is the result to verify, and `details` provides the following:
* `expected`: `unknown` (*OPTIONAL*) — What `actual` is expected to be
* `positiveOp`: `string` — The operation description if a positive (not `.not`) test fails
* `negativeOp`: `string` — The operation description if a negatved (`.not`) test fails
* `message`: `string` (*OPTIONAL*) — The messge—in its entirety—to use if the test fails
The `.check()` method—by default—tests if `result` is truthy, and throws an `AssertionError` if it is not.  If the `not` flag is applied, it instead tests if `result` is falsy, and throws an `AssertionError` if it is not.  If `message` is not provided, the rest of `details` is used to construct the error message.
### Helpers
The following protected members are available to mixins to aid in checks:
* `.flags(): string[]` — Retrieves a snapshot of currently-set flags on this Expecto.  A flag is set if its name is in the returned array.
* `.hasFlag(flag: string): boolean` — Returns `true` if the given flag is set.
* `.setFlag(flag: string)` — Sets the given flag, including it in the values returned by `flags()`.
* `.unsetFlag(flag: string)`— Removes the given flag, excluding it in the values returned by `flags()`.
* `.toggleFlag(flag: string): boolean` — Toggles the given flag; sets it if it was not before, or unsets it if it was; retursn the current state (`true` for set, `false` otherwise).
* `.create<T>(actual: T): this` — Creates a new Expecto of the same type as this Expecto, using `actual` as the target value and with all the flags currently set on this Expecto.