Skip to content

Instantly share code, notes, and snippets.

@peerhenry
Created January 21, 2019 15:25
Show Gist options
  • Select an option

  • Save peerhenry/a8dd01226114758936aef009bd95527f to your computer and use it in GitHub Desktop.

Select an option

Save peerhenry/a8dd01226114758936aef009bd95527f to your computer and use it in GitHub Desktop.
a script that provides easy mocking of components, pipes and services
import { Type, Component, Pipe, EventEmitter } from '@angular/core';
const ANNOTATIONS = '__annotations__';
const PARAMETERS = '__parameters__';
const PROP_METADATA = '__prop__metadata__';
export type Mocked<T> = { [P in keyof T]: T[P] };
type Constructor<T> = Function & { prototype: T };
export function mockType<T>(type: Constructor<T>, overrides: Partial<T> = {}): Constructor<Mocked<T>> {
const mock = class {};
Object.defineProperty(mock, 'name', { value: `Mock${type.name}`});
const originalProperties = Object.getOwnPropertyDescriptors(type.prototype);
const targetProperties: { [x: string]: PropertyDescriptor } = {};
for (const propertyName of Object.keys(originalProperties)) {
const originalProperty = originalProperties[propertyName];
if (originalProperty.enumerable === false) {
continue;
}
const targetProperty: PropertyDescriptor = convertProperty(originalProperty);
targetProperties[propertyName] = targetProperty;
}
for (const propertyName of Object.keys(overrides)) {
targetProperties[propertyName] = {
configurable: true,
enumerable: true,
value: overrides[propertyName]
};
}
Object.defineProperties(mock.prototype, targetProperties);
return <Type<Mocked<T>>>mock;
}
function convertProperty(originalProperty: TypedPropertyDescriptor<any> & PropertyDescriptor) {
const targetProperty: PropertyDescriptor = { enumerable: true, configurable: true };
if ('value' in originalProperty) {
originalProperty.writable = originalProperty.writable;
if (originalProperty.value instanceof Function) {
targetProperty.value = () => { };
} else {
targetProperty.value = originalProperty.value;
}
} else {
let storage;
targetProperty.get = () => storage;
if ('set' in originalProperty) {
targetProperty.set = (value: any) => storage = value;
}
}
return targetProperty;
}
interface ProvideType<T> { provide: Constructor<T>; useClass: Constructor<Mocked<T>>; }
export function mockProvide<T>(type: Constructor<T>, overrides: Partial<T> = {}): ProvideType<T> {
return {
provide: type,
useClass: mockType(type, overrides)
};
}
export const provideMocked = mockProvide;
export function mockComponent<T>(type: Constructor<T>, overrides: Partial<T> = {}): Constructor<Mocked<T>> {
if (!(ANNOTATIONS in type)) {
throw new Error('Cannot decorate non-component');
}
const component = <Component>(type as any)[ANNOTATIONS][0];
return this.mockClassToComponent({
selector: component.selector,
providers: component.providers
}, type, overrides);
}
type CArg = Component & { selector: string };
export function mockClassToComponent<T>(selector: string, type: Constructor<T>, overrides?: Partial<T>): Constructor<Mocked<T>>;
// tslint:disable-next-line:unified-signatures
export function mockClassToComponent<T>(component: CArg, type: Constructor<T>, overrides?: Partial<T>): Constructor<Mocked<T>>;
export function mockClassToComponent<T>(arg: string | CArg, type: Constructor<T>, overrides: Partial<T> = {}): Constructor<Mocked<T>> {
if (typeof arg === 'string') {
arg = { selector: arg };
}
const mock = mockType(type, overrides);
const componentMock = <Constructor<Mocked<T>>><any>Component({
template: `<p>mocked component with selector ${arg.selector}</p>`,
...arg,
})(mock);
const incognito = (componentMock as any);
incognito[PROP_METADATA] = (type as any)[PROP_METADATA];
incognito.ngBaseDef = (type as any).ngBaseDef;
if (incognito.ngBaseDef) {
for (const key of Object.keys(incognito.ngBaseDef.outputs)) {
Object.defineProperty(componentMock.prototype, key, {
value: new EventEmitter()
});
}
}
return incognito;
}
export function mockPipe<T>(type: Constructor<T>, overrides: Partial<T> = {}): Constructor<Mocked<T>> {
if (!(ANNOTATIONS in type)) {
throw new Error('Cannot decorate non-decorated pipe');
}
const pipe = <Pipe>(type as any)[ANNOTATIONS][0];
const name = pipe.name;
return this.mockClassToPipe(name, type);
}
export function mockClassToPipe<T>(name: string, type: Constructor<T>, overrides: Partial<T> = {}): Constructor<Mocked<T>> {
const mock = mockType(type, overrides);
const pipeMock = <Constructor<Mocked<T>>><any>Pipe({
name: name
})(mock);
(pipeMock as any)[PROP_METADATA] = (type as any)[PROP_METADATA];
(pipeMock as any).ngBaseDef = (type as any).ngBaseDef;
return pipeMock;
}
// todo: mock directive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment