mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 08:33:43 -05:00
feat(std/testing): Add support for object assertion against object subset (#8001)
This commit add supports for a new assertion function "assertObjectMatch" which allows to test an actual object against an expected object subset (i.e. inclusivity, not equality).
This commit is contained in:
parent
322913ee5c
commit
23df1c563e
3 changed files with 219 additions and 0 deletions
|
@ -25,6 +25,8 @@ pretty-printed diff of failing assertion.
|
|||
`expected`.
|
||||
- `assertArrayContains()` - Make an assertion that `actual` array contains the
|
||||
`expected` values.
|
||||
- `assertObjectMatch()` - Make an assertion that `actual` object match
|
||||
`expected` subset object
|
||||
- `assertThrows()` - Expects the passed `fn` to throw. If `fn` does not throw,
|
||||
this function does. Also compares any errors thrown to an optional expected
|
||||
`Error` class and checks that the error `.message` includes an optional
|
||||
|
|
|
@ -429,6 +429,48 @@ export function assertNotMatch(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` object is a subset of `expected` object, deeply.
|
||||
* If not, then throw.
|
||||
*/
|
||||
export function assertObjectMatch(
|
||||
actual: Record<PropertyKey, unknown>,
|
||||
expected: Record<PropertyKey, unknown>,
|
||||
): void {
|
||||
type loose = Record<PropertyKey, unknown>;
|
||||
const seen = new WeakMap();
|
||||
return assertEquals(
|
||||
(function filter(a: loose, b: loose): loose {
|
||||
// Prevent infinite loop with circular references with same filter
|
||||
if ((seen.has(a)) && (seen.get(a) === b)) {
|
||||
return a;
|
||||
}
|
||||
seen.set(a, b);
|
||||
// Filter keys and symbols which are present in both actual and expected
|
||||
const filtered = {} as loose;
|
||||
const entries = [
|
||||
...Object.getOwnPropertyNames(a),
|
||||
...Object.getOwnPropertySymbols(a),
|
||||
]
|
||||
.filter((key) => key in b)
|
||||
.map((key) => [key, a[key as string]]) as Array<[string, unknown]>;
|
||||
// Build filtered object and filter recursively on nested objects references
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof value === "object") {
|
||||
const subset = (b as loose)[key];
|
||||
if ((typeof subset === "object") && (subset)) {
|
||||
filtered[key] = filter(value as loose, subset as loose);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filtered[key] = value;
|
||||
}
|
||||
return filtered;
|
||||
})(actual, expected),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcefully throws a failed assertion
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
assertNotEquals,
|
||||
assertNotMatch,
|
||||
assertNotStrictEquals,
|
||||
assertObjectMatch,
|
||||
assertStrictEquals,
|
||||
assertStringContains,
|
||||
assertThrows,
|
||||
|
@ -259,6 +260,180 @@ Deno.test("testingAssertStringNotMatchingThrows", function (): void {
|
|||
assert(didThrow);
|
||||
});
|
||||
|
||||
Deno.test("testingAssertObjectMatching", function (): void {
|
||||
const sym = Symbol("foo");
|
||||
const a = { foo: true, bar: false };
|
||||
const b = { ...a, baz: a };
|
||||
const c = { ...b, qux: b };
|
||||
const d = { corge: c, grault: c };
|
||||
const e = { foo: true } as { [key: string]: unknown };
|
||||
e.bar = e;
|
||||
const f = { [sym]: true, bar: false };
|
||||
// Simple subset
|
||||
assertObjectMatch(a, {
|
||||
foo: true,
|
||||
});
|
||||
// Subset with another subset
|
||||
assertObjectMatch(b, {
|
||||
foo: true,
|
||||
baz: { bar: false },
|
||||
});
|
||||
// Subset with multiple subsets
|
||||
assertObjectMatch(c, {
|
||||
foo: true,
|
||||
baz: { bar: false },
|
||||
qux: {
|
||||
baz: { foo: true },
|
||||
},
|
||||
});
|
||||
// Subset with same object reference as subset
|
||||
assertObjectMatch(d, {
|
||||
corge: {
|
||||
foo: true,
|
||||
qux: { bar: false },
|
||||
},
|
||||
grault: {
|
||||
bar: false,
|
||||
qux: { foo: true },
|
||||
},
|
||||
});
|
||||
// Subset with circular reference
|
||||
assertObjectMatch(e, {
|
||||
foo: true,
|
||||
bar: {
|
||||
bar: {
|
||||
bar: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// Subset with same symbol
|
||||
assertObjectMatch(f, {
|
||||
[sym]: true,
|
||||
});
|
||||
// Missing key
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch({
|
||||
foo: true,
|
||||
}, {
|
||||
foo: true,
|
||||
bar: false,
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Simple subset
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(a, {
|
||||
foo: false,
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Subset with another subset
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(b, {
|
||||
foo: true,
|
||||
baz: { bar: true },
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Subset with multiple subsets
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(c, {
|
||||
foo: true,
|
||||
baz: { bar: false },
|
||||
qux: {
|
||||
baz: { foo: false },
|
||||
},
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Subset with same object reference as subset
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(d, {
|
||||
corge: {
|
||||
foo: true,
|
||||
qux: { bar: true },
|
||||
},
|
||||
grault: {
|
||||
bar: false,
|
||||
qux: { foo: false },
|
||||
},
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Subset with circular reference
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(e, {
|
||||
foo: true,
|
||||
bar: {
|
||||
bar: {
|
||||
bar: {
|
||||
foo: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
// Subset with symbol key but with string key subset
|
||||
{
|
||||
let didThrow;
|
||||
try {
|
||||
assertObjectMatch(f, {
|
||||
foo: true,
|
||||
});
|
||||
didThrow = false;
|
||||
} catch (e) {
|
||||
assert(e instanceof AssertionError);
|
||||
didThrow = true;
|
||||
}
|
||||
assertEquals(didThrow, true);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("testingAssertsUnimplemented", function (): void {
|
||||
let didThrow = false;
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue