Skip to content

Instantly share code, notes, and snippets.

@kylefender
Last active September 12, 2022 15:41
Show Gist options
  • Save kylefender/7e0a730ef1a1c4fef2f70bec4cb2d2bd to your computer and use it in GitHub Desktop.
Save kylefender/7e0a730ef1a1c4fef2f70bec4cb2d2bd to your computer and use it in GitHub Desktop.
Jasmine extensions that make creating and interacting with mocked methods and properties easier. These are based on an article by Andrei Mihalciuc (https://itnext.io/better-typescript-support-for-jasmine-20dc7454ba94)
// Based on https://itnext.io/better-typescript-support-for-jasmine-20dc7454ba94
import { Pipe, Type } from '@angular/core';
import { TestBed } from '@angular/core/testing';
export type Spied<T> = T & { [Method in keyof T]: jasmine.Spy };
export function spyOnClass<T>(spiedClass: Type<T>): Spied<T> {
const prototype = spiedClass.prototype;
let methodNames = Object.getOwnPropertyNames(prototype)
.map(name => [name, Object.getOwnPropertyDescriptor(prototype, name)])
.filter(([name, descriptor]) => {
return (descriptor as PropertyDescriptor).value instanceof Function;
})
.map(([name]) => name);
let propertyNames = Object.getOwnPropertyNames(prototype)
.map(name => [name, Object.getOwnPropertyDescriptor(prototype, name)])
.filter(([name, descriptor]) => {
return (
(descriptor as PropertyDescriptor).get instanceof Function
|| (descriptor as PropertyDescriptor).set instanceof Function
);
})
.map(([name]) => name);
// Walk-through all of the object's prototypes
let proto = prototype.__proto__;
while (proto !== Object.prototype) {
const protoMethods = Object.getOwnPropertyNames(proto)
.map(name => [name, Object.getOwnPropertyDescriptor(proto, name)])
.filter(([name, descriptor]) => {
return (descriptor as PropertyDescriptor).value instanceof Function;
})
.map(([name]) => name.toString());
const protoProperties = Object.getOwnPropertyNames(proto)
.map(name => [name, Object.getOwnPropertyDescriptor(proto, name)])
.filter(([name, descriptor]) => {
return (
(descriptor as PropertyDescriptor).get instanceof Function
|| (descriptor as PropertyDescriptor).set instanceof Function
);
})
.map(([name]) => name.toString());
methodNames = methodNames.concat(protoMethods);
propertyNames = propertyNames.concat(protoProperties);
proto = proto.__proto__;
}
// Grab just the unique values for methods and properties
return jasmine.createSpyObj(spiedClass.toString(), [...new Set(methodNames)], [...new Set(propertyNames)]);
}
export function provideMock<T>(spiedClass: Type<T>): Object {
return {
provide: spiedClass,
useValue: spyOnClass(spiedClass)
};
}
export function getMock<T>(spiedClass: Type<T>): Spied<T> {
return TestBed.inject<any>(spiedClass) as Spied<T>;
}
export function getMockedProperty<T>(spiedClass: Spied<T>, propertyName: string, accessType?: 'get' | 'set'): jasmine.Spy {
if (accessType === 'set') {
return Object.getOwnPropertyDescriptor(spiedClass, propertyName).set as jasmine.Spy;
}
return Object.getOwnPropertyDescriptor(spiedClass, propertyName).get as jasmine.Spy;
}
export function addMockedProperty<T>(spiedClass: Spied<T>, name: string, value: any, linkSetterToGetter?: boolean): void {
Object.defineProperty(spiedClass, name, {
get: function () { return value; },
set: linkSetterToGetter === true ? function (setterValue: any) { value = setterValue; } : function (setterValue: any) { }
});
}
export function addMockedProperties<T>(spiedClass: Spied<T>, properties: { [key: string]: any }, linkSetterToGetter?: boolean): void {
Object.getOwnPropertyNames(properties).map(name => addMockedProperty(spiedClass, name, properties[name], linkSetterToGetter));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment