feat(testing): add assertInstanceOf (#2028)

This commit is contained in:
Jesper van den Ende 2022-03-16 06:38:30 +01:00 committed by GitHub
parent 8f560b4887
commit 8e01ec6cb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 158 additions and 0 deletions

View File

@ -20,6 +20,8 @@ pretty-printed diff of failing assertion.
for non-primitives the values must reference the same instance.
- `assertAlmostEquals()` - Make an assertion that `actual` is almost equal to
`expected`, according to a given `epsilon` _(defaults to `1e-7`)_
- `assertInstanceOf()` - Make an assertion that `actual` is an instance of
`expectedType`.
- `assertStringIncludes()` - Make an assertion that `actual` includes
`expected`.
- `assertMatch()` - Make an assertion that `actual` match RegExp `expected`.

View File

@ -438,6 +438,48 @@ delta "${f(delta)}" is greater than "${f(tolerance)}"`,
);
}
// deno-lint-ignore no-explicit-any
type AnyConstructor = new (...args: any[]) => any;
type GetConstructorType<T extends AnyConstructor> = T extends // deno-lint-ignore no-explicit-any
new (...args: any) => infer C ? C
: never;
/**
* Make an assertion that `obj` is an instance of `type`.
* If not then throw.
*/
export function assertInstanceOf<T extends AnyConstructor>(
actual: unknown,
expectedType: T,
msg = "",
): asserts actual is GetConstructorType<T> {
if (!msg) {
const expectedTypeStr = expectedType.name;
let actualTypeStr = "";
if (actual === null) {
actualTypeStr = "null";
} else if (actual === undefined) {
actualTypeStr = "undefined";
} else if (typeof actual === "object") {
actualTypeStr = actual.constructor?.name ?? "Object";
} else {
actualTypeStr = typeof actual;
}
if (expectedTypeStr == actualTypeStr) {
msg = `Expected object to be an instance of "${expectedTypeStr}".`;
} else if (actualTypeStr == "function") {
msg =
`Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object.`;
} else {
msg =
`Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}".`;
}
}
assert(actual instanceof expectedType, msg);
}
/**
* Make an assertion that actual is not null or undefined.
* If not then throw.

View File

@ -6,6 +6,7 @@ import {
assertArrayIncludes,
assertEquals,
assertExists,
assertInstanceOf,
AssertionError,
assertIsError,
assertMatch,
@ -1016,6 +1017,119 @@ Deno.test("assert almost equals number", () => {
);
});
Deno.test({
name: "assertInstanceOf",
fn(): void {
class TestClass1 {}
class TestClass2 {}
// Regular types
assertInstanceOf(new Date(), Date);
assertInstanceOf(new Number(), Number);
assertInstanceOf(Promise.resolve(), Promise);
assertInstanceOf(new TestClass1(), TestClass1);
// Throwing cases
assertThrows(
() => assertInstanceOf(new Date(), RegExp),
AssertionError,
`Expected object to be an instance of "RegExp" but was "Date".`,
);
assertThrows(
() => assertInstanceOf(5, Date),
AssertionError,
`Expected object to be an instance of "Date" but was "number".`,
);
assertThrows(
() => assertInstanceOf(new TestClass1(), TestClass2),
AssertionError,
`Expected object to be an instance of "TestClass2" but was "TestClass1".`,
);
// Custom message
assertThrows(
() => assertInstanceOf(new Date(), RegExp, "Custom message"),
AssertionError,
"Custom message",
);
// Edge cases
assertThrows(
() => assertInstanceOf(5, Number),
AssertionError,
`Expected object to be an instance of "Number" but was "number".`,
);
let TestClassWithSameName: new () => unknown;
{
class TestClass1 {}
TestClassWithSameName = TestClass1;
}
assertThrows(
() => assertInstanceOf(new TestClassWithSameName(), TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1".`,
);
assertThrows(
() => assertInstanceOf(TestClass1, TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was not an instanced object.`,
);
assertThrows(
() => assertInstanceOf(() => {}, TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was not an instanced object.`,
);
assertThrows(
() => assertInstanceOf(null, TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was "null".`,
);
assertThrows(
() => assertInstanceOf(undefined, TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was "undefined".`,
);
assertThrows(
() => assertInstanceOf({}, TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was "Object".`,
);
assertThrows(
() => assertInstanceOf(Object.create(null), TestClass1),
AssertionError,
`Expected object to be an instance of "TestClass1" but was "Object".`,
);
// Test TypeScript types functionality, wrapped in a function that never runs
// deno-lint-ignore no-unused-vars
function typeScriptTests() {
class ClassWithProperty {
property = "prop1";
}
const testInstance = new ClassWithProperty() as unknown;
// @ts-expect-error: `testInstance` is `unknown` so setting its property before `assertInstanceOf` should give a type error.
testInstance.property = "prop2";
assertInstanceOf(testInstance, ClassWithProperty);
// Now `testInstance` should be of type `ClassWithProperty`
testInstance.property = "prop3";
let x = 5 as unknown;
// @ts-expect-error: `x` is `unknown` so adding to it shouldn't work
x += 5;
assertInstanceOf(x, Number);
// @ts-expect-error: `x` is now `Number` rather than `number`, so this should still give a type error.
x += 5;
}
},
});
Deno.test({
name: "assert* functions with specified type parameter",
fn(): void {