Skip to content

Commit

Permalink
feat: mock-service is typed and supports overrides
Browse files Browse the repository at this point in the history
closes #122
  • Loading branch information
satanTime committed May 19, 2020
1 parent af1c51f commit 805e37b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 12 deletions.
15 changes: 13 additions & 2 deletions lib/mock-helper/mock-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const MockHelper: {
findOrFail<T = any>(debugElement: MockedDebugElement, cssSelector: string): MockedDebugElement<T>;
getDirective<T>(debugNode: MockedDebugNode, directive: Type<T>): undefined | T;
getDirectiveOrFail<T>(debugNode: MockedDebugNode, directive: Type<T>): T;
mockService<I extends object, O extends object>(instance: I, overrides: O): I & O;
mockService<T = MockedFunction>(instance: any, name: string, style?: 'get' | 'set'): T;
} = {
getDirectiveOrFail: <T>(debugNode: MockedDebugNode, directive: Type<T>): T => {
Expand Down Expand Up @@ -118,6 +119,16 @@ export const MockHelper: {
return el.queryAll(term);
},

mockService: <T = MockedFunction>(instance: any, name: string, style?: 'get' | 'set'): T =>
mockServiceHelper.mock(instance, name, style),
mockService: <T = MockedFunction>(instance: any, override: string | object, style?: 'get' | 'set'): T => {
if (typeof override === 'string') {
return mockServiceHelper.mock(instance, override, style);
}
for (const key of Object.getOwnPropertyNames(override)) {
const def = Object.getOwnPropertyDescriptor(override, key);
if (def) {
Object.defineProperty(instance, key, def);
}
}
return instance;
},
};
54 changes: 46 additions & 8 deletions lib/mock-service/mock-service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// tslint:disable:max-classes-per-file

import { InjectionToken } from '@angular/core';

import { MockHelper } from '../mock-helper';

import { MockService } from './mock-service';

// tslint:disable:max-classes-per-file
class DeepParentClass {
public deepParentMethodName = 'deepParentMethod';

Expand Down Expand Up @@ -58,8 +59,6 @@ class GetterSetterMethodHuetod {
}
}

// tslint:enable:max-classes-per-file

describe('MockService', () => {
it('should convert boolean, number, string, null and undefined to undefined', () => {
expect(MockService(true)).toBeUndefined();
Expand Down Expand Up @@ -102,7 +101,9 @@ describe('MockService', () => {
// all methods should be defined as functions which return undefined.
expect(mockedService.deepParentMethod).toEqual(jasmine.any(Function), 'deepParentMethod');
expect(mockedService.deepParentMethod()).toBeUndefined('deepParentMethod()');
expect(mockedService.deepParentMethod.and.identity()).toBe('DeepParentClass.deepParentMethod');
expect(MockHelper.mockService<any>(mockedService, 'deepParentMethod').and.identity()).toBe(
'DeepParentClass.deepParentMethod'
);
});

it('should mock own and parent methods of a class', () => {
Expand All @@ -118,16 +119,18 @@ describe('MockService', () => {
// all methods should be defined as functions which return undefined.
expect(mockedService.deepParentMethod).toEqual(jasmine.any(Function), 'deepParentMethod');
expect(mockedService.deepParentMethod()).toBeUndefined('deepParentMethod()');
expect(mockedService.deepParentMethod.and.identity()).toBe('ChildClass.deepParentMethod');
expect(MockHelper.mockService<any>(mockedService, 'deepParentMethod').and.identity()).toBe(
'ChildClass.deepParentMethod'
);
expect(mockedService.parentMethod).toEqual(jasmine.any(Function), 'parentMethod');
expect(mockedService.parentMethod()).toBeUndefined('parentMethod()');
expect(mockedService.parentMethod.and.identity()).toBe('ChildClass.parentMethod');
expect(MockHelper.mockService<any>(mockedService, 'parentMethod').and.identity()).toBe('ChildClass.parentMethod');
expect(mockedService.overrideMe).toEqual(jasmine.any(Function), 'overrideMe');
expect(mockedService.overrideMe()).toBeUndefined('overrideMe()');
expect(mockedService.overrideMe.and.identity()).toBe('ChildClass.overrideMe');
expect(MockHelper.mockService<any>(mockedService, 'overrideMe').and.identity()).toBe('ChildClass.overrideMe');
expect(mockedService.childMethod).toEqual(jasmine.any(Function), 'childMethod');
expect(mockedService.childMethod()).toBeUndefined('childMethod()');
expect(mockedService.childMethod.and.identity()).toBe('ChildClass.childMethod');
expect(MockHelper.mockService<any>(mockedService, 'childMethod').and.identity()).toBe('ChildClass.childMethod');
});

it('should mock an instance of a class as an object', () => {
Expand Down Expand Up @@ -232,4 +235,39 @@ describe('MockService', () => {
const token1 = MockService(new InjectionToken('hello'));
expect(token1).toBeUndefined();
});

it('mocks a class to an instance with proper types', () => {
class Test {
public readonly nameRead = 'read';

private name = 'test';

public get nameGet(): string {
return this.name;
}

public set nameSet(name: string) {
this.name = name;
}

public echo(): string {
return this.name;
}
}

const test = MockHelper.mockService(MockService(Test), {
echo: jasmine.createSpy().and.returnValue('fake1'),
fake: jasmine.createSpy().and.returnValue('fake2'),
nameGet: 'fake3',
nameRead: 'fake4',
nameSet: 'fake5',
});

expect(test).toEqual(jasmine.any(Test));
expect(test.echo()).toBe('fake1');
expect((test as any).fake()).toBe('fake2');
expect(test.nameGet).toBe('fake3');
expect(test.nameRead).toBe('fake4');
expect(test.nameSet).toBe('fake5');
});
});
6 changes: 4 additions & 2 deletions lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ export const mockServiceHelper: {
} = ((window as any) || (global as any)).ngMocksMockServiceHelper;

export function MockService(service?: boolean | number | string | null, mockNamePrefix?: string): undefined;
export function MockService<T extends {}>(service: T, mockNamePrefix?: string): any;
export function MockService<T>(service: new (...args: any[]) => T, mockNamePrefix?: string): T;
// tslint:disable-next-line:no-misused-new unified-signatures
export function MockService<T = any>(service: object, mockNamePrefix?: string): T;
export function MockService(service: any, mockNamePrefix?: string): any {
// mocking all methods / properties of a class / object.
let value: any;
Expand All @@ -228,7 +230,7 @@ export function MockService(service: any, mockNamePrefix?: string): any {
? localHelper.createMockFromPrototype(service.constructor.prototype)
: {};
for (const property of Object.keys(service)) {
const mock = MockService(service[property], `${mockNamePrefix ? mockNamePrefix : 'instance'}.${property}`);
const mock: any = MockService(service[property], `${mockNamePrefix ? mockNamePrefix : 'instance'}.${property}`);
if (mock !== undefined) {
value[property] = mock;
}
Expand Down

0 comments on commit 805e37b

Please sign in to comment.