Skip to content

Commit 4071083

Browse files
authored
Merge pull request #37 from lambdalisue/better-default-message
👍 Improve the assertion error message
2 parents 2e392ad + 4a59511 commit 4071083

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)