Skip to content

Commit ef8a534

Browse files
committed
fix(assert): hint at reference-equality when diff is empty (#6878)
`assertEquals` throws "Values are not equal" but renders an empty diff when actual/expected stringify identically yet are deemed unequal — the common case is a function property compared by reference. The empty diff leaves users hunting for invisible differences. Detect the case (diff result has zero `added`/`removed` entries) and append a one-line hint explaining that functions, Promises, Requests, Blobs, and other built-ins are compared by reference, so two distinct instances are never equal even when their representations match. Skip the hint on real textual diffs and on string-vs-string comparisons (which always produce visible add/remove if they differ). Two new tests cover the hint firing path and the negative case. Full assert suite (141 tests) stays green. Direction matches @lionel-rowe's suggestion in the issue thread.
1 parent f0c9f14 commit ef8a534

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

assert/equals.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,17 @@ export function assertEquals<T>(
6464
const diffMsg = buildMessage(diffResult, { stringDiff }, arguments[3])
6565
.join("\n");
6666
message = `${message}\n${diffMsg}`;
67+
// #6878: if the diff shows no removed/added lines, the values stringify
68+
// identically but were still deemed unequal — typically because at least
69+
// one nested property is a function (or Promise / Request / Blob / etc.)
70+
// that gets compared by reference. The empty diff is confusing, so append
71+
// a hint pointing at the likely cause.
72+
if (!stringDiff && diffResult.every((r) => r.type !== "added" && r.type !== "removed")) {
73+
message = `${message}\n` +
74+
" Note: values stringify identically but are not structurally equal. " +
75+
"Functions, Promises, Requests, Blobs, and other built-ins are compared by " +
76+
"reference, so two distinct instances are never equal even when their " +
77+
"representations match.";
78+
}
6779
throw new AssertionError(message);
6880
}

assert/equals_test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// Copyright 2018-2026 the Deno authors. MIT license.
2-
import { assertEquals, AssertionError, assertThrows } from "./mod.ts";
2+
import {
3+
assertEquals,
4+
AssertionError,
5+
assertStringIncludes,
6+
assertThrows,
7+
} from "./mod.ts";
38
import {
49
bold,
510
gray,
@@ -207,6 +212,48 @@ Deno.test({
207212
},
208213
});
209214

215+
Deno.test({
216+
name:
217+
"assertEquals() hints at reference-equality when objects with function props stringify identically (#6878)",
218+
fn() {
219+
let caught: AssertionError | undefined;
220+
try {
221+
assertEquals(
222+
{ x: 1, y: () => 2 },
223+
{ x: 1, y: () => 2 },
224+
);
225+
} catch (e) {
226+
caught = e as AssertionError;
227+
}
228+
if (!caught) throw new Error("Expected assertEquals to throw");
229+
assertStringIncludes(caught.message, "Values are not equal");
230+
assertStringIncludes(
231+
caught.message,
232+
"stringify identically but are not structurally equal",
233+
);
234+
assertStringIncludes(caught.message, "compared by reference");
235+
},
236+
});
237+
238+
Deno.test({
239+
name:
240+
"assertEquals() does not append reference-equality hint when there is a real textual diff",
241+
fn() {
242+
let caught: AssertionError | undefined;
243+
try {
244+
assertEquals({ x: 1 }, { x: 2 });
245+
} catch (e) {
246+
caught = e as AssertionError;
247+
}
248+
if (!caught) throw new Error("Expected assertEquals to throw");
249+
assertEquals(
250+
caught.message.includes("stringify identically"),
251+
false,
252+
"Hint should only fire when the diff is empty",
253+
);
254+
},
255+
});
256+
210257
Deno.test({
211258
name: "assertEquals() matches same Set with object keys",
212259
fn() {

0 commit comments

Comments
 (0)