Skip to content

Commit f5795f8

Browse files
authored
fix(expect): include diff when expect.toMatchObject throws (#6525)
1 parent 1db0c55 commit f5795f8

File tree

4 files changed

+122
-20
lines changed

4 files changed

+122
-20
lines changed

expect/_build_message.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ function isString(value: unknown): value is string {
1818
export function buildEqualErrorMessage<T>(
1919
actual: T,
2020
expected: T,
21-
options: EqualErrorMessageOptions,
21+
options: EqualErrorMessageOptions = {},
2222
): string {
23-
const { formatter = format, msg } = options ?? {};
23+
const { formatter = format, msg } = options;
2424
const msgPrefix = msg ? `${msg}: ` : "";
2525
const actualString = formatter(actual);
2626
const expectedString = formatter(expected);
@@ -40,11 +40,11 @@ export function buildEqualErrorMessage<T>(
4040
export function buildNotEqualErrorMessage<T>(
4141
actual: T,
4242
expected: T,
43-
options: EqualErrorMessageOptions,
43+
options: EqualErrorMessageOptions = {},
4444
): string {
45-
const { msg } = options ?? {};
46-
const actualString = String(actual);
47-
const expectedString = String(expected);
45+
const { formatter = format, msg } = options;
46+
const actualString = formatter(actual);
47+
const expectedString = formatter(expected);
4848

4949
const msgPrefix = msg ? `${msg}: ` : "";
5050
return `${msgPrefix}Expected actual: ${actualString} not to be: ${expectedString}.`;

expect/_matchers.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import {
2121
iterableEquality,
2222
subsetEquality,
2323
} from "./_utils.ts";
24+
import {
25+
buildEqualErrorMessage,
26+
buildNotEqualErrorMessage,
27+
} from "./_build_message.ts";
2428

2529
export function toBe(context: MatcherContext, expect: unknown): MatchResult {
2630
if (context.isNot) {
@@ -565,7 +569,7 @@ export function toMatchObject(
565569
);
566570
}
567571

568-
const pass = equal(context.value, expected, {
572+
const pass = equal(received, expected, {
569573
strictCheck: false,
570574
customTesters: [
571575
...context.customTesters,
@@ -575,20 +579,15 @@ export function toMatchObject(
575579
});
576580

577581
const triggerError = () => {
578-
const actualString = format(context.value);
579-
const expectedString = format(expected);
580-
581582
if (context.isNot) {
582-
const defaultMessage =
583-
`Expected ${actualString} to NOT match ${expectedString}`;
583+
const defaultMessage = buildNotEqualErrorMessage(received, expected);
584584
throw new AssertionError(
585585
context.customMessage
586586
? `${context.customMessage}: ${defaultMessage}`
587587
: defaultMessage,
588588
);
589589
} else {
590-
const defaultMessage =
591-
`Expected ${actualString} to match ${expectedString}`;
590+
const defaultMessage = buildEqualErrorMessage(received, expected);
592591
throw new AssertionError(
593592
context.customMessage
594593
? `${context.customMessage}: ${defaultMessage}`

expect/_to_match_object_test.ts

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Deno.test("expect().toMatchObject() with array", () => {
6565
]);
6666
});
6767

68-
Deno.test("expect(),toMatchObject() with asyAsymmetric matcher", () => {
68+
Deno.test("expect().toMatchObject() with asymmetric matcher", () => {
6969
expect({ position: { x: 0, y: 0 } }).toMatchObject({
7070
position: {
7171
x: expect.any(Number),
@@ -119,15 +119,112 @@ Deno.test("expect().toMatchObject() throws the correct error messages", () => {
119119
{
120120
const e = assertThrows(
121121
() => expect({ a: 1 }).toMatchObject({ a: 2 }),
122-
Error,
122+
AssertionError,
123123
);
124-
assertNotMatch(e.message, /NOT/);
124+
assertNotMatch(e.message, /not to be/);
125125
}
126126
{
127127
const e = assertThrows(
128128
() => expect({ a: 1 }).not.toMatchObject({ a: 1 }),
129-
Error,
129+
AssertionError,
130130
);
131-
assertMatch(e.message, /NOT/);
131+
assertMatch(e.message, /not to be/);
132132
}
133133
});
134+
135+
Deno.test("expect().toMatchObject() displays a diff", async (t) => {
136+
await t.step("with expect.any", () => {
137+
assertThrows(
138+
() =>
139+
expect({ a: "a" })
140+
.toMatchObject({ a: expect.any(Number) }),
141+
AssertionError,
142+
` {
143+
+ a: Any {
144+
+ inverse: false,
145+
+ value: [Function: Number],
146+
+ },
147+
- a: "a",
148+
}`,
149+
);
150+
});
151+
152+
await t.step("with nested properties", () => {
153+
const x = {
154+
command: "error",
155+
payload: {
156+
message: "NodeNotFound",
157+
},
158+
protocol: "graph",
159+
};
160+
161+
const y = {
162+
protocol: "graph",
163+
command: "addgroup",
164+
payload: {
165+
graph: "foo",
166+
metadata: {
167+
description: "foo",
168+
},
169+
name: "somegroup",
170+
nodes: [
171+
"somenode",
172+
"someothernode",
173+
],
174+
},
175+
};
176+
177+
assertThrows(
178+
() => expect(x).toMatchObject(y),
179+
AssertionError,
180+
` {
181+
+ command: "addgroup",
182+
- command: "error",
183+
payload: {
184+
+ graph: "foo",
185+
+ metadata: {
186+
+ description: "foo",
187+
+ },
188+
+ name: "somegroup",
189+
+ nodes: [
190+
+ "somenode",
191+
+ "someothernode",
192+
+ ],
193+
- message: "NodeNotFound",
194+
},
195+
protocol: "graph",
196+
}`,
197+
);
198+
199+
assertThrows(
200+
() => expect({ foo: [] }).toMatchObject({ foo: ["bar"] }),
201+
AssertionError,
202+
` {
203+
+ foo: [
204+
+ "bar",
205+
+ ],
206+
- foo: [],
207+
}`,
208+
);
209+
});
210+
211+
await t.step(
212+
"with `__proto__`",
213+
() => {
214+
const objectA = { ["__proto__"]: { polluted: true } };
215+
const objectB = { ["__proto__"]: { polluted: true } };
216+
const objectC = { ["__proto__"]: { polluted: false } };
217+
expect(objectA).toMatchObject(objectB);
218+
assertThrows(
219+
() => expect(objectA).toMatchObject(objectC),
220+
AssertionError,
221+
` {
222+
['__proto__']: {
223+
- polluted: true,
224+
+ polluted: false,
225+
},
226+
}`,
227+
);
228+
},
229+
);
230+
});

internal/format_test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Deno.test("format() overrides default behaviours of Deno.inspect", async (t) =>
2727
// Wraps objects into multiple lines even when they are small. Prints trailing
2828
// commas.
2929
await t.step(
30-
"fromat() always wraps objects into multiple lines and prints trailing commas",
30+
"format() always wraps objects into multiple lines and prints trailing commas",
3131
() =>
3232
assertEquals(
3333
stripAnsiCode(format({ a: 1, b: 2 })),
@@ -38,6 +38,12 @@ Deno.test("format() overrides default behaviours of Deno.inspect", async (t) =>
3838
),
3939
);
4040

41+
await t.step("format() sorts properties", () =>
42+
assertEquals(
43+
format({ b: 2, a: 1 }),
44+
format({ a: 1, b: 2 }),
45+
));
46+
4147
await t.step("format() wraps Object with getters", () =>
4248
assertEquals(
4349
format(Object.defineProperty({}, "a", {

0 commit comments

Comments
 (0)