Skip to content

Commit 4a59511

Browse files
committed
👍 Improve the assertion error message
Now the default assertion error message contains the information about the type of value, predicate name, and the value itself. Additionally, users can change the factory function with `setAssertMessageFactory`.
1 parent 2e392ad commit 4a59511

File tree

2 files changed

+102
-10
lines changed

2 files changed

+102
-10
lines changed

util.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import type { Predicate } from "./is.ts";
22

3+
export type AssertMessageFactory = (
4+
x: unknown,
5+
pred: Predicate<unknown>,
6+
) => string;
7+
8+
export const defaultAssertMessageFactory: AssertMessageFactory = (x, pred) => {
9+
const p = pred.name || "anonymous predicate";
10+
const t = typeof x;
11+
const v = JSON.stringify(x, null, 2);
12+
return `Expected a value that satisfies the predicate ${p}, got ${t}: ${v}`;
13+
};
14+
15+
let assertMessageFactory = defaultAssertMessageFactory;
16+
317
/**
418
* Represents an error that occurs when an assertion fails.
519
*/
@@ -19,6 +33,31 @@ export class AssertError extends Error {
1933
}
2034
}
2135

36+
/**
37+
* Sets the factory function used to generate assertion error messages.
38+
* @param factory The factory function.
39+
* @example
40+
* ```ts
41+
* import { setAssertMessageFactory } from "./util.ts";
42+
* import is from "./is.ts";
43+
*
44+
* setAssertMessageFactory((x, pred) => {
45+
* if (pred === is.String) {
46+
* return `Expected a string, got ${typeof x}`;
47+
* } else if (pred === is.Number) {
48+
* return `Expected a number, got ${typeof x}`;
49+
* } else if (pred === is.Boolean) {
50+
* return `Expected a boolean, got ${typeof x}`;
51+
* } else {
52+
* return `Expected a value that satisfies the predicate, got ${typeof x}`;
53+
* }
54+
* });
55+
* ```
56+
*/
57+
export function setAssertMessageFactory(factory: AssertMessageFactory): void {
58+
assertMessageFactory = factory;
59+
}
60+
2261
/**
2362
* Asserts that the given value satisfies the provided predicate.
2463
*
@@ -44,7 +83,7 @@ export function assert<T>(
4483
): asserts x is T {
4584
if (!pred(x)) {
4685
throw new AssertError(
47-
options.message ?? "The value is not the expected type",
86+
options.message ?? assertMessageFactory(x, pred),
4887
);
4988
}
5089
}

util_test.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,51 @@ import {
22
assertStrictEquals,
33
assertThrows,
44
} from "https://deno.land/[email protected]/testing/asserts.ts";
5-
import { assert, AssertError, ensure, maybe } from "./util.ts";
5+
import {
6+
assert,
7+
AssertError,
8+
defaultAssertMessageFactory,
9+
ensure,
10+
maybe,
11+
setAssertMessageFactory,
12+
} from "./util.ts";
613

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

16+
function truePredicate(_x: unknown): _x is string {
17+
return true;
18+
}
19+
20+
function falsePredicate(_x: unknown): _x is string {
21+
return false;
22+
}
23+
924
Deno.test("assert", async (t) => {
1025
await t.step("does nothing on true predicate", () => {
11-
assert(x, (_x): _x is string => true);
26+
assert(x, truePredicate);
1227
});
1328

1429
await t.step("throws an `AssertError` on false predicate", () => {
15-
assertThrows(() => assert(x, (_x): _x is string => false), AssertError);
30+
assertThrows(
31+
() => assert(x, falsePredicate),
32+
AssertError,
33+
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
34+
);
35+
});
36+
37+
await t.step("throws an `AssertError` on false predicate", () => {
38+
assertThrows(
39+
() => assert(x, falsePredicate),
40+
AssertError,
41+
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
42+
);
1643
});
1744

1845
await t.step(
1946
"throws an `AssertError` with a custom message on false predicate",
2047
() => {
2148
assertThrows(
22-
() => assert(x, (_x): _x is string => false, { message: "Hello" }),
49+
() => assert(x, falsePredicate, { message: "Hello" }),
2350
AssertError,
2451
"Hello",
2552
);
@@ -29,18 +56,22 @@ Deno.test("assert", async (t) => {
2956

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

3562
await t.step("throws an `AssertError` on false predicate", () => {
36-
assertThrows(() => ensure(x, (_x): _x is string => false), AssertError);
63+
assertThrows(
64+
() => ensure(x, falsePredicate),
65+
AssertError,
66+
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
67+
);
3768
});
3869

3970
await t.step(
4071
"throws an `AssertError` with a custom message on false predicate",
4172
() => {
4273
assertThrows(
43-
() => ensure(x, (_x): _x is string => false, { message: "Hello" }),
74+
() => ensure(x, falsePredicate, { message: "Hello" }),
4475
AssertError,
4576
"Hello",
4677
);
@@ -50,10 +81,32 @@ Deno.test("ensure", async (t) => {
5081

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

5687
await t.step("returns `undefined` on false predicate", () => {
57-
assertStrictEquals(maybe(x, (_x): _x is string => false), undefined);
88+
assertStrictEquals(maybe(x, falsePredicate), undefined);
5889
});
5990
});
91+
92+
Deno.test("setAssertMessageFactory", async (t) => {
93+
setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`);
94+
95+
await t.step("change `AssertError` message on `assert` failure", () => {
96+
assertThrows(
97+
() => assert(x, falsePredicate),
98+
AssertError,
99+
"Hello symbol falsePredicate",
100+
);
101+
});
102+
103+
await t.step("change `AssertError` message on `ensure` failure", () => {
104+
assertThrows(
105+
() => ensure(x, falsePredicate),
106+
AssertError,
107+
"Hello symbol falsePredicate",
108+
);
109+
});
110+
111+
setAssertMessageFactory(defaultAssertMessageFactory);
112+
});

0 commit comments

Comments
 (0)