Skip to content

Instantly share code, notes, and snippets.

@jfhector
Created February 24, 2022 14:07
Show Gist options
  • Save jfhector/7cb97a85bede8324c1f23a4775f54a0a to your computer and use it in GitHub Desktop.
Save jfhector/7cb97a85bede8324c1f23a4775f54a0a to your computer and use it in GitHub Desktop.
TypeScript assertion functions: assertHasProperty and assertIsAnObject
/* eslint-disable @typescript-eslint/no-empty-function */
import { assertHasProperty } from '../';
test(`Given it's called with an object that has the specified property
And that property does NOT hold 'undefined'
Then it does NOT throw`, () => {
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', {
nameOfThePropertyImLookingFor: {},
});
}).not.toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', {
nameOfThePropertyImLookingFor: '',
});
}).not.toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', {
nameOfThePropertyImLookingFor: null,
});
}).not.toThrow();
});
test(`Given it's called with something that does NOT have the specified property
Then it throws`, () => {
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', {});
}).toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', 42);
}).toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', []);
}).toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', undefined);
}).toThrow();
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', () => {});
}).toThrow();
});
test(`Given it's called with an object that has the specified property
And that property does holds 'undefined'
Then it throw`, () => {
expect(() => {
assertHasProperty('nameOfThePropertyImLookingFor', {
nameOfThePropertyImLookingFor: undefined,
});
}).not.toThrow();
});
/**
* Use this to check that an object has a specific property called, and that it does not hold `undefined`.
*
* ['Assertion functions'](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions)
* like this one make it easy to help TypeScript narrow types, without loosing type safety.
*
* Alternative options to help TypeScript would be:
* - [user defined type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards). But with user defined type guards, the type narrowing only happens withn the same JS expression. With assertion functions, the type narrowing happens within the entire scope.
*
* - type assertions (`as`) or type declarations (`declare`). But you'd lose type safety.
*/
function assertHasProperty<PropertyName extends string>(
propertyName: PropertyName,
obj: any
): asserts obj is {
[k: string]: unknown;
propertyName: unknown;
} {
if (!(propertyName in obj))
throw new Error(
`Unexpectedly, there's no "${propertyName}" property on this thing: ${obj}`
);
}
export { assertHasProperty };
/**
* Use this to check that something is an object.
*
* Note: in JavaScript functions are objects too, even though `typeof myFunction == 'function'` rather than `... == 'object'`. This helper recognises functions as objects.
*
* ['Assertion functions'](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions)
* like this one make it easy to help TypeScript narrow types, without loosing type safety.
*
* Alternative options to help TypeScript would be:
* - [user defined type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards). But with user defined type guards, the type narrowing only happens withn the same JS expression. With assertion functions, the type narrowing happens within the entire scope.
*
* - type assertions (`as`) or type declarations (`declare`). But you'd lose type safety.
*/
function assertIsAnObject(obj: any): asserts obj is Record<string, unknown> {
const isAnObject =
(typeof obj == 'object' && obj !== null) /* 1 */ ||
typeof obj == 'function'; /* 2 */
if (!isAnObject)
throw new Error(`Unexpectedly, this is not an object: ${obj}`);
}
export { assertIsAnObject };
/**
* Note 1: Because in JavaScript `typeof null == 'object'`
*/
/**
* Note 2: Because in JavaScript functions are valid objects too (even though `typeof myFunction == 'function'` rather than `== 'object'`)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment