Skip to main content
Module

std/testing/asserts.ts

Deno standard library
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
/** A library of assertion functions. * If the assertion is false an `AssertionError` will be thrown which will * result in pretty-printed diff of failing assertion. * * This module is browser compatible, but do not rely on good formatting of * values for AssertionError messages in browsers. * * @module */
import { red, stripColor } from "../fmt/colors.ts";import { buildMessage, diff, diffstr } from "./_diff.ts";import { format } from "./_format.ts";
const CAN_NOT_DISPLAY = "[Cannot display]";
export class AssertionError extends Error { override name = "AssertionError"; constructor(message: string) { super(message); }}
function isKeyedCollection(x: unknown): x is Set<unknown> { return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));}
/** * Deep equality comparison used in assertions * @param c actual value * @param d expected value */export function equal(c: unknown, d: unknown): boolean { const seen = new Map(); return (function compare(a: unknown, b: unknown): boolean { // Have to render RegExp & Date for string comparison // unless it's mistreated as object if ( a && b && ((a instanceof RegExp && b instanceof RegExp) || (a instanceof URL && b instanceof URL)) ) { return String(a) === String(b); } if (a instanceof Date && b instanceof Date) { const aTime = a.getTime(); const bTime = b.getTime(); // Check for NaN equality manually since NaN is not // equal to itself. if (Number.isNaN(aTime) && Number.isNaN(bTime)) { return true; } return aTime === bTime; } if (typeof a === "number" && typeof b === "number") { return Number.isNaN(a) && Number.isNaN(b) || a === b; } if (Object.is(a, b)) { return true; } if (a && typeof a === "object" && b && typeof b === "object") { if (a && b && !constructorsEqual(a, b)) { return false; } if (a instanceof WeakMap || b instanceof WeakMap) { if (!(a instanceof WeakMap && b instanceof WeakMap)) return false; throw new TypeError("cannot compare WeakMap instances"); } if (a instanceof WeakSet || b instanceof WeakSet) { if (!(a instanceof WeakSet && b instanceof WeakSet)) return false; throw new TypeError("cannot compare WeakSet instances"); } if (seen.get(a) === b) { return true; } if (Object.keys(a || {}).length !== Object.keys(b || {}).length) { return false; } seen.set(a, b); if (isKeyedCollection(a) && isKeyedCollection(b)) { if (a.size !== b.size) { return false; }
let unmatchedEntries = a.size;
for (const [aKey, aValue] of a.entries()) { for (const [bKey, bValue] of b.entries()) { /* Given that Map keys can be references, we need * to ensure that they are also deeply equal */ if ( (aKey === aValue && bKey === bValue && compare(aKey, bKey)) || (compare(aKey, bKey) && compare(aValue, bValue)) ) { unmatchedEntries--; break; } } }
return unmatchedEntries === 0; } const merged = { ...a, ...b }; for ( const key of [ ...Object.getOwnPropertyNames(merged), ...Object.getOwnPropertySymbols(merged), ] ) { type Key = keyof typeof merged; if (!compare(a && a[key as Key], b && b[key as Key])) { return false; } if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) { return false; } } if (a instanceof WeakRef || b instanceof WeakRef) { if (!(a instanceof WeakRef && b instanceof WeakRef)) return false; return compare(a.deref(), b.deref()); } return true; } return false; })(c, d);}
// deno-lint-ignore ban-typesfunction constructorsEqual(a: object, b: object) { return a.constructor === b.constructor || a.constructor === Object && !b.constructor || !a.constructor && b.constructor === Object;}
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */export function assert(expr: unknown, msg = ""): asserts expr { if (!expr) { throw new AssertionError(msg); }}
/** Make an assertion, error will be thrown if `expr` have truthy value. */type Falsy = false | 0 | 0n | "" | null | undefined;export function assertFalse(expr: unknown, msg = ""): asserts expr is Falsy { if (expr) { throw new AssertionError(msg); }}
/** * Make an assertion that `actual` and `expected` are equal, deeply. If not * deeply equal, then throw. * * Type parameter can be specified to ensure values under comparison have the same type. * * @example * ```ts * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("example", function (): void { * assertEquals("world", "world"); * assertEquals({ hello: "world" }, { hello: "world" }); * }); * ``` */export function assertEquals<T>(actual: T, expected: T, msg?: string) { if (equal(actual, expected)) { return; } let message = ""; const actualString = format(actual); const expectedString = format(expected); try { const stringDiff = (typeof actual === "string") && (typeof expected === "string"); const diffResult = stringDiff ? diffstr(actual as string, expected as string) : diff(actualString.split("\n"), expectedString.split("\n")); const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); message = `Values are not equal:\n${diffMsg}`; } catch { message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`; } if (msg) { message = msg; } throw new AssertionError(message);}
/** * Make an assertion that `actual` and `expected` are not equal, deeply. * If not then throw. * * Type parameter can be specified to ensure values under comparison have the same type. * * @example * ```ts * import { assertNotEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * assertNotEquals<number>(1, 2) * ``` */export function assertNotEquals<T>(actual: T, expected: T, msg?: string) { if (!equal(actual, expected)) { return; } let actualString: string; let expectedString: string; try { actualString = String(actual); } catch { actualString = "[Cannot display]"; } try { expectedString = String(expected); } catch { expectedString = "[Cannot display]"; } if (!msg) { msg = `actual: ${actualString} expected not to be: ${expectedString}`; } throw new AssertionError(msg);}
/** * Make an assertion that `actual` and `expected` are strictly equal. If * not then throw. * * @example * ```ts * import { assertStrictEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("isStrictlyEqual", function (): void { * const a = {}; * const b = a; * assertStrictEquals(a, b); * }); * * // This test fails * Deno.test("isNotStrictlyEqual", function (): void { * const a = {}; * const b = {}; * assertStrictEquals(a, b); * }); * ``` */export function assertStrictEquals<T>( actual: unknown, expected: T, msg?: string,): asserts actual is T { if (Object.is(actual, expected)) { return; }
let message: string;
if (msg) { message = msg; } else { const actualString = format(actual); const expectedString = format(expected);
if (actualString === expectedString) { const withOffset = actualString .split("\n") .map((l) => ` ${l}`) .join("\n"); message = `Values have the same structure but are not reference-equal:\n\n${ red(withOffset) }\n`; } else { try { const stringDiff = (typeof actual === "string") && (typeof expected === "string"); const diffResult = stringDiff ? diffstr(actual as string, expected as string) : diff(actualString.split("\n"), expectedString.split("\n")); const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); message = `Values are not strictly equal:\n${diffMsg}`; } catch { message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`; } } }
throw new AssertionError(message);}
/** * Make an assertion that `actual` and `expected` are not strictly equal. * If the values are strictly equal then throw. * * ```ts * import { assertNotStrictEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * assertNotStrictEquals(1, 1) * ``` */export function assertNotStrictEquals<T>( actual: T, expected: T, msg?: string,) { if (!Object.is(actual, expected)) { return; }
throw new AssertionError( msg ?? `Expected "actual" to be strictly unequal to: ${format(actual)}\n`, );}
/** * Make an assertion that `actual` and `expected` are almost equal numbers through * a given tolerance. It can be used to take into account IEEE-754 double-precision * floating-point representation limitations. * If the values are not almost equal then throw. * * @example * ```ts * import { assertAlmostEquals, assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * assertAlmostEquals(0.1, 0.2); * * // Using a custom tolerance value * assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16); * assertThrows(() => assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17)); * ``` */export function assertAlmostEquals( actual: number, expected: number, tolerance = 1e-7, msg?: string,) { if (Object.is(actual, expected)) { return; } const delta = Math.abs(expected - actual); if (delta <= tolerance) { return; } const f = (n: number) => Number.isInteger(n) ? n : n.toExponential(); throw new AssertionError( msg ?? `actual: "${f(actual)}" expected to be close to "${f(expected)}": \delta "${f(delta)}" is greater than "${f(tolerance)}"`, );}
// deno-lint-ignore no-explicit-anytype AnyConstructor = new (...args: any[]) => any;type GetConstructorType<T extends AnyConstructor> = T extends // deno-lint-ignore no-explicit-anynew (...args: any) => infer C ? C : never;
/** * Make an assertion that `obj` is an instance of `type`. * If not then throw. */export function assertInstanceOf<T extends AnyConstructor>( actual: unknown, expectedType: T, msg = "",): asserts actual is GetConstructorType<T> { if (!msg) { const expectedTypeStr = expectedType.name;
let actualTypeStr = ""; if (actual === null) { actualTypeStr = "null"; } else if (actual === undefined) { actualTypeStr = "undefined"; } else if (typeof actual === "object") { actualTypeStr = actual.constructor?.name ?? "Object"; } else { actualTypeStr = typeof actual; }
if (expectedTypeStr == actualTypeStr) { msg = `Expected object to be an instance of "${expectedTypeStr}".`; } else if (actualTypeStr == "function") { msg = `Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object.`; } else { msg = `Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}".`; } } assert(actual instanceof expectedType, msg);}
/** * Make an assertion that `obj` is not an instance of `type`. * If so, then throw. */export function assertNotInstanceOf<A, T>( actual: A, // deno-lint-ignore no-explicit-any unexpectedType: new (...args: any[]) => T, msg = `Expected object to not be an instance of "${typeof unexpectedType}"`,): asserts actual is Exclude<A, T> { assertFalse(actual instanceof unexpectedType, msg);}
/** * Make an assertion that actual is not null or undefined. * If not then throw. */export function assertExists<T>( actual: T, msg?: string,): asserts actual is NonNullable<T> { if (actual === undefined || actual === null) { if (!msg) { msg = `actual: "${actual}" expected to not be null or undefined`; } throw new AssertionError(msg); }}
/** * Make an assertion that actual includes expected. If not * then throw. */export function assertStringIncludes( actual: string, expected: string, msg?: string,) { if (!actual.includes(expected)) { if (!msg) { msg = `actual: "${actual}" expected to contain: "${expected}"`; } throw new AssertionError(msg); }}
/** * Make an assertion that `actual` includes the `expected` values. * If not then an error will be thrown. * * Type parameter can be specified to ensure values under comparison have the same type. * * @example * ```ts * import { assertArrayIncludes } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * assertArrayIncludes<number>([1, 2], [2]) * ``` */export function assertArrayIncludes<T>( actual: ArrayLike<T>, expected: ArrayLike<T>, msg?: string,) { const missing: unknown[] = []; for (let i = 0; i < expected.length; i++) { let found = false; for (let j = 0; j < actual.length; j++) { if (equal(expected[i], actual[j])) { found = true; break; } } if (!found) { missing.push(expected[i]); } } if (missing.length === 0) { return; } if (!msg) { msg = `actual: "${format(actual)}" expected to include: "${ format(expected) }"\nmissing: ${format(missing)}`; } throw new AssertionError(msg);}
/** * Make an assertion that `actual` match RegExp `expected`. If not * then throw. */export function assertMatch( actual: string, expected: RegExp, msg?: string,) { if (!expected.test(actual)) { if (!msg) { msg = `actual: "${actual}" expected to match: "${expected}"`; } throw new AssertionError(msg); }}
/** * Make an assertion that `actual` not match RegExp `expected`. If match * then throw. */export function assertNotMatch( actual: string, expected: RegExp, msg?: string,) { if (expected.test(actual)) { if (!msg) { msg = `actual: "${actual}" expected to not match: "${expected}"`; } throw new AssertionError(msg); }}
/** * Make an assertion that `actual` object is a subset of `expected` object, deeply. * If not, then throw. */export function assertObjectMatch( // deno-lint-ignore no-explicit-any actual: Record<PropertyKey, any>, expected: Record<PropertyKey, unknown>,) { type loose = Record<PropertyKey, unknown>;
function filter(a: loose, b: loose) { const seen = new WeakMap(); return fn(a, b);
function fn(a: loose, b: loose): loose { // Prevent infinite loop with circular references with same filter if ((seen.has(a)) && (seen.get(a) === b)) { return a; } seen.set(a, b); // Filter keys and symbols which are present in both actual and expected const filtered = {} as loose; const entries = [ ...Object.getOwnPropertyNames(a), ...Object.getOwnPropertySymbols(a), ] .filter((key) => key in b) .map((key) => [key, a[key as string]]) as Array<[string, unknown]>; for (const [key, value] of entries) { // On array references, build a filtered array and filter nested objects inside if (Array.isArray(value)) { const subset = (b as loose)[key]; if (Array.isArray(subset)) { filtered[key] = fn({ ...value }, { ...subset }); continue; } } // On regexp references, keep value as it to avoid loosing pattern and flags else if (value instanceof RegExp) { filtered[key] = value; continue; } // On nested objects references, build a filtered object recursively else if (typeof value === "object") { const subset = (b as loose)[key]; if ((typeof subset === "object") && (subset)) { // When both operands are maps, build a filtered map with common keys and filter nested objects inside if ((value instanceof Map) && (subset instanceof Map)) { filtered[key] = new Map( [...value].filter(([k]) => subset.has(k)).map(( [k, v], ) => [k, typeof v === "object" ? fn(v, subset.get(k)) : v]), ); continue; } // When both operands are set, build a filtered set with common values if ((value instanceof Set) && (subset instanceof Set)) { filtered[key] = new Set([...value].filter((v) => subset.has(v))); continue; } filtered[key] = fn(value as loose, subset as loose); continue; } } filtered[key] = value; } return filtered; } } return assertEquals( // get the intersection of "actual" and "expected" // side effect: all the instances' constructor field is "Object" now. filter(actual, expected), // set (nested) instances' constructor field to be "Object" without changing expected value. // see https://github.com/denoland/deno_std/pull/1419 filter(expected, expected), );}
/** * Forcefully throws a failed assertion */export function fail(msg?: string): never { assert(false, `Failed assertion${msg ? `: ${msg}` : "."}`);}
/** * Make an assertion that `error` is an `Error`. * If not then an error will be thrown. * An error class and a string that should be included in the * error message can also be asserted. */export function assertIsError<E extends Error = Error>( error: unknown, // deno-lint-ignore no-explicit-any ErrorClass?: new (...args: any[]) => E, msgIncludes?: string, msg?: string,): asserts error is E { if (error instanceof Error === false) { throw new AssertionError(`Expected "error" to be an Error object.`); } if (ErrorClass && !(error instanceof ErrorClass)) { msg = `Expected error to be instance of "${ErrorClass.name}", but was "${ typeof error === "object" ? error?.constructor?.name : "[not an object]" }"${msg ? `: ${msg}` : "."}`; throw new AssertionError(msg); } if ( msgIncludes && (!(error instanceof Error) || !stripColor(error.message).includes(stripColor(msgIncludes))) ) { msg = `Expected error message to include "${msgIncludes}", but got "${ error instanceof Error ? error.message : "[not an Error]" }"${msg ? `: ${msg}` : "."}`; throw new AssertionError(msg); }}
/** * Executes a function, expecting it to throw. If it does not, then it * throws. * * @example * ```ts * import { assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("doesThrow", function (): void { * assertThrows((): void => { * throw new TypeError("hello world!"); * }); * }); * * // This test will not pass. * Deno.test("fails", function (): void { * assertThrows((): void => { * console.log("Hello world"); * }); * }); * ``` */export function assertThrows( fn: () => unknown, msg?: string,): unknown;/** * Executes a function, expecting it to throw. If it does not, then it * throws. An error class and a string that should be included in the * error message can also be asserted. * * @example * * ```ts * import { assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("doesThrow", function (): void { * assertThrows((): void => { * throw new TypeError("hello world!"); * }, TypeError); * assertThrows( * (): void => { * throw new TypeError("hello world!"); * }, * TypeError, * "hello", * ); * }); * * // This test will not pass. * Deno.test("fails", function (): void { * assertThrows((): void => { * console.log("Hello world"); * }); * }); * ``` */export function assertThrows<E extends Error = Error>( fn: () => unknown, // deno-lint-ignore no-explicit-any ErrorClass: new (...args: any[]) => E, msgIncludes?: string, msg?: string,): E;export function assertThrows<E extends Error = Error>( fn: () => unknown, errorClassOrMsg?: // deno-lint-ignore no-explicit-any | (new (...args: any[]) => E) | string, msgIncludesOrMsg?: string, msg?: string,): E | Error | unknown { // deno-lint-ignore no-explicit-any let ErrorClass: (new (...args: any[]) => E) | undefined = undefined; let msgIncludes: string | undefined = undefined; let err;
if (typeof errorClassOrMsg !== "string") { if ( errorClassOrMsg === undefined || errorClassOrMsg.prototype instanceof Error || errorClassOrMsg.prototype === Error.prototype ) { // deno-lint-ignore no-explicit-any ErrorClass = errorClassOrMsg as new (...args: any[]) => E; msgIncludes = msgIncludesOrMsg; } else { msg = msgIncludesOrMsg; } } else { msg = errorClassOrMsg; } let doesThrow = false; const msgToAppendToError = msg ? `: ${msg}` : "."; try { fn(); } catch (error) { if (ErrorClass) { if (error instanceof Error === false) { throw new AssertionError("A non-Error object was thrown."); } assertIsError( error, ErrorClass, msgIncludes, msg, ); } err = error; doesThrow = true; } if (!doesThrow) { msg = `Expected function to throw${msgToAppendToError}`; throw new AssertionError(msg); } return err;}
/** * Executes a function which returns a promise, expecting it to reject. * * @example * ```ts * import { assertRejects } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("doesThrow", async function () { * await assertRejects( * async () => { * throw new TypeError("hello world!"); * }, * ); * await assertRejects( * async () => { * return Promise.reject(new Error()); * }, * ); * }); * * // This test will not pass. * Deno.test("fails", async function () { * await assertRejects( * async () => { * console.log("Hello world"); * }, * ); * }); * ``` */export function assertRejects( fn: () => PromiseLike<unknown>, msg?: string,): Promise<unknown>;/** * Executes a function which returns a promise, expecting it to reject. * If it does not, then it throws. An error class and a string that should be * included in the error message can also be asserted. * * @example * ```ts * import { assertRejects } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; * * Deno.test("doesThrow", async function () { * await assertRejects(async () => { * throw new TypeError("hello world!"); * }, TypeError); * await assertRejects( * async () => { * throw new TypeError("hello world!"); * }, * TypeError, * "hello", * ); * }); * * // This test will not pass. * Deno.test("fails", async function () { * await assertRejects( * async () => { * console.log("Hello world"); * }, * ); * }); * ``` */export function assertRejects<E extends Error = Error>( fn: () => PromiseLike<unknown>, // deno-lint-ignore no-explicit-any ErrorClass: new (...args: any[]) => E, msgIncludes?: string, msg?: string,): Promise<E>;export async function assertRejects<E extends Error = Error>( fn: () => PromiseLike<unknown>, errorClassOrMsg?: // deno-lint-ignore no-explicit-any | (new (...args: any[]) => E) | string, msgIncludesOrMsg?: string, msg?: string,): Promise<E | Error | unknown> { // deno-lint-ignore no-explicit-any let ErrorClass: (new (...args: any[]) => E) | undefined = undefined; let msgIncludes: string | undefined = undefined; let err;
if (typeof errorClassOrMsg !== "string") { if ( errorClassOrMsg === undefined || errorClassOrMsg.prototype instanceof Error || errorClassOrMsg.prototype === Error.prototype ) { // deno-lint-ignore no-explicit-any ErrorClass = errorClassOrMsg as new (...args: any[]) => E; msgIncludes = msgIncludesOrMsg; } } else { msg = errorClassOrMsg; } let doesThrow = false; let isPromiseReturned = false; const msgToAppendToError = msg ? `: ${msg}` : "."; try { const possiblePromise = fn(); if ( possiblePromise && typeof possiblePromise === "object" && typeof possiblePromise.then === "function" ) { isPromiseReturned = true; await possiblePromise; } } catch (error) { if (!isPromiseReturned) { throw new AssertionError( `Function throws when expected to reject${msgToAppendToError}`, ); } if (ErrorClass) { if (error instanceof Error === false) { throw new AssertionError("A non-Error object was rejected."); } assertIsError( error, ErrorClass, msgIncludes, msg, ); } err = error; doesThrow = true; } if (!doesThrow) { throw new AssertionError( `Expected function to reject${msgToAppendToError}`, ); } return err;}
/** Use this to stub out methods that will throw when invoked. */export function unimplemented(msg?: string): never { throw new AssertionError(msg || "unimplemented");}
/** Use this to assert unreachable code. */export function unreachable(): never { throw new AssertionError("unreachable");}