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/expecto@v0.1.0/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/expecto@v0.1.0/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; exportsexpectanduse, as well as theAssertionErrorclass in use. Without any calls touse(), the Expecto returned byexpect()is initialized with thecore,typing,membership, andpromisedassertions.mod/mocked.ts(mock) — This exports the (default)mockedassertion mixin that can be applied viause. It also exportsmockwhich is the std/testing/mock implementation it depends on. NOTE that this requiresmod/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(()=>{constspied=mocked.spy(nestedFunc);constresult=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/analsoandbebeendoeshas/haveisofthattowhichwith
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(()=>{thrownewError()}).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(()=>thrownewError("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(()=>thrownewTypeError("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(()=>thrownewError("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(()=>thrownewTypeError("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(()=>thrownewTypeError("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 TypeErrorexpect(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();// SUCCEEDSexpect("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();// SUCCEEDSexpect("").to.be.false();// FAILSexpect(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 NaNexpect(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:
bigintbooleannumberobjectstring
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(newDate()).is.an.instanceOf(RegExp,"not a Regexp!");If not is applied beforehand, it negates the check.
expect(newDate()).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 0Array/Typed Array (e.g.,Uint8Array) — checks.lengthis 0ArrayBuffer— checks.byteLengthis 0string— checks.lengthis 0object— 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 TypeErrorexpect(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 onactualconstsomeValue=newMap();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 SetactualconstsomeValue=newSet();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 arrayactualconstsomeValue=["foo","bar"]; ....expect(someValue).to.have.members(["foo","bar"]);
Object: checks that all of the given members are properties onactualconstsomeValue={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.
constsomeValue=["foo","bar"];
....expect(someValue).to.have.any.members(["foo","baz"]);// SUCCEEDSexpect(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.
constsomeValue=newSet([{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 TypeErrorexpect(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 TypeErrorexpect(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 TypeErrorexpect(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).
awaitexpect(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).
awaitexpect(somePromsie).to.be.rejected();A custom message can be provided, which is used if the check fails.
awaitexpect(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.
constsomePromise=Promise.reject(newError("oops!"));
....awaitexpect(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.
constsomePromise=Promise.resolve("some string");
....awaitexpect(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).
awaitexpect(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.
awaitexpect(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.
awaitexpect(()=>Promise.reject(newTypeError("wrong type"))).to.be.rejectedWith(TypeError);A custom message can be provided as the last argument, which is used if the check fails.
awaitexpect(()=>Promise.reject(newRangeError("out of bounds"))).to.be.rejectedWith(TypeError,"not a type error!");If not is applied beforehand, it negates the check.
awaitexpect(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 TypeErrorexpect("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"}]);// failsexpect(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 TypeErrorexpect("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.
importtype{CheckDetails,ExpectoConstructor}from"https://deno.land/x/expecto/mod/mixin.ts";import{meetsExpectations}from"./custom.ts";exportfunctioncustomMixin<TargetType,BaseTypeextendsExpectoConstructor<TargetType>>(Base: BaseType){returnclassCustomMixinextendsBase{constructor(...args: any[]){super(...args);}cusomCheck(): this {letresult=meetsExpectations(this.actual);this.check(result,{positiveOp: "does not meet expectations",negativeOp: "meets expectations",}asCheckDetails)returnthis;}};}
### PerformingChecksTheassertioncheckisperformed using `.check(result: boolean, details: CheckDetails)`;`result`istheresulttoverify,and`details`providesthefollowing:
*`expected`: `unknown`(*OPTIONAL*)—What`actual`isexpectedtobe*`positiveOp`: `string`—Theoperationdescriptionifapositive(not`.not`)testfails*`negativeOp`: `string`—Theoperationdescriptionifanegatved(`.not`)testfails*`message`: `string`(*OPTIONAL*)—Themessge—initsentirety—touseifthetestfailsThe`.check()`method—bydefault—testsif`result`istruthy,andthrowsan`AssertionError`ifit is not.Ifthe`not`flag is applied,itinsteadtestsif`result`isfalsy,andthrowsan`AssertionError`ifit is not.If`message`isnotprovided,therestof`details`isusedtoconstructtheerrormessage.
### HelpersThefollowingprotectedmembersareavailabletomixinstoaidinchecks:
*`.flags(): string[]`—Retrievesasnapshotofcurrently-setflagsonthisExpecto.Aflag is setifitsnameisinthereturnedarray.*`.hasFlag(flag: string): boolean`—Returns`true`ifthegivenflag is set.*`.setFlag(flag: string)`—Setsthegivenflag,includingitinthevaluesreturnedby`flags()`.*`.unsetFlag(flag: string)`—Removesthegivenflag,excludingitinthevaluesreturnedby`flags()`.*`.toggleFlag(flag: string): boolean`—Togglesthegivenflag;setsitifitwasnotbefore,orunsetsitifitwas;retursnthecurrentstate(`true`forset,`false`otherwise).*`.create<T>(actual: T): this`—CreatesanewExpectoofthesametypeas this Expecto, using `actual`asthetargetvalueandwithalltheflagscurrentlysetonthisExpecto.