Skip to content

Commit

Permalink
Merge pull request #37 from lambdalisue/better-default-message
Browse files Browse the repository at this point in the history
👍 Improve the assertion error message
  • Loading branch information
lambdalisue authored Aug 28, 2023
2 parents 2e392ad + 4a59511 commit 4071083
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 10 deletions.
41 changes: 40 additions & 1 deletion util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import type { Predicate } from "./is.ts";

export type AssertMessageFactory = (
x: unknown,
pred: Predicate<unknown>,
) => string;

export const defaultAssertMessageFactory: AssertMessageFactory = (x, pred) => {
const p = pred.name || "anonymous predicate";
const t = typeof x;
const v = JSON.stringify(x, null, 2);
return `Expected a value that satisfies the predicate ${p}, got ${t}: ${v}`;
};

let assertMessageFactory = defaultAssertMessageFactory;

/**
* Represents an error that occurs when an assertion fails.
*/
Expand All @@ -19,6 +33,31 @@ export class AssertError extends Error {
}
}

/**
* Sets the factory function used to generate assertion error messages.
* @param factory The factory function.
* @example
* ```ts
* import { setAssertMessageFactory } from "./util.ts";
* import is from "./is.ts";
*
* setAssertMessageFactory((x, pred) => {
* if (pred === is.String) {
* return `Expected a string, got ${typeof x}`;
* } else if (pred === is.Number) {
* return `Expected a number, got ${typeof x}`;
* } else if (pred === is.Boolean) {
* return `Expected a boolean, got ${typeof x}`;
* } else {
* return `Expected a value that satisfies the predicate, got ${typeof x}`;
* }
* });
* ```
*/
export function setAssertMessageFactory(factory: AssertMessageFactory): void {
assertMessageFactory = factory;
}

/**
* Asserts that the given value satisfies the provided predicate.
*
Expand All @@ -44,7 +83,7 @@ export function assert<T>(
): asserts x is T {
if (!pred(x)) {
throw new AssertError(
options.message ?? "The value is not the expected type",
options.message ?? assertMessageFactory(x, pred),
);
}
}
Expand Down
71 changes: 62 additions & 9 deletions util_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,51 @@ import {
assertStrictEquals,
assertThrows,
} from "https://deno.land/[email protected]/testing/asserts.ts";
import { assert, AssertError, ensure, maybe } from "./util.ts";
import {
assert,
AssertError,
defaultAssertMessageFactory,
ensure,
maybe,
setAssertMessageFactory,
} from "./util.ts";

const x: unknown = Symbol("x");

function truePredicate(_x: unknown): _x is string {
return true;
}

function falsePredicate(_x: unknown): _x is string {
return false;
}

Deno.test("assert", async (t) => {
await t.step("does nothing on true predicate", () => {
assert(x, (_x): _x is string => true);
assert(x, truePredicate);
});

await t.step("throws an `AssertError` on false predicate", () => {
assertThrows(() => assert(x, (_x): _x is string => false), AssertError);
assertThrows(
() => assert(x, falsePredicate),
AssertError,
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
);
});

await t.step("throws an `AssertError` on false predicate", () => {
assertThrows(
() => assert(x, falsePredicate),
AssertError,
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
);
});

await t.step(
"throws an `AssertError` with a custom message on false predicate",
() => {
assertThrows(
() => assert(x, (_x): _x is string => false, { message: "Hello" }),
() => assert(x, falsePredicate, { message: "Hello" }),
AssertError,
"Hello",
);
Expand All @@ -29,18 +56,22 @@ Deno.test("assert", async (t) => {

Deno.test("ensure", async (t) => {
await t.step("returns `x` as-is on true predicate", () => {
assertStrictEquals(ensure(x, (_x): _x is string => true), x);
assertStrictEquals(ensure(x, truePredicate), x);
});

await t.step("throws an `AssertError` on false predicate", () => {
assertThrows(() => ensure(x, (_x): _x is string => false), AssertError);
assertThrows(
() => ensure(x, falsePredicate),
AssertError,
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
);
});

await t.step(
"throws an `AssertError` with a custom message on false predicate",
() => {
assertThrows(
() => ensure(x, (_x): _x is string => false, { message: "Hello" }),
() => ensure(x, falsePredicate, { message: "Hello" }),
AssertError,
"Hello",
);
Expand All @@ -50,10 +81,32 @@ Deno.test("ensure", async (t) => {

Deno.test("maybe", async (t) => {
await t.step("returns `x` as-is on true predicate", () => {
assertStrictEquals(maybe(x, (_x): _x is string => true), x);
assertStrictEquals(maybe(x, truePredicate), x);
});

await t.step("returns `undefined` on false predicate", () => {
assertStrictEquals(maybe(x, (_x): _x is string => false), undefined);
assertStrictEquals(maybe(x, falsePredicate), undefined);
});
});

Deno.test("setAssertMessageFactory", async (t) => {
setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`);

await t.step("change `AssertError` message on `assert` failure", () => {
assertThrows(
() => assert(x, falsePredicate),
AssertError,
"Hello symbol falsePredicate",
);
});

await t.step("change `AssertError` message on `ensure` failure", () => {
assertThrows(
() => ensure(x, falsePredicate),
AssertError,
"Hello symbol falsePredicate",
);
});

setAssertMessageFactory(defaultAssertMessageFactory);
});

0 comments on commit 4071083

Please sign in to comment.