Skip to content

Commit

Permalink
feat: original instanceof and properties
Browse files Browse the repository at this point in the history
closes #109

# Conflicts:
#	README.md
  • Loading branch information
satanTime committed May 17, 2020
1 parent f1fcaa4 commit 05dd90b
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 56 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,23 +509,33 @@ describe('MockService', () => {
expect(mock).toBeDefined();

// Creating a mock on the getter.
MockHelper.mockService<jasmine.Spy>(mock, 'name', 'get').and.returnValue('mock');
spyOnProperty(mock, 'name', 'get').and.returnValue('mock');
expect(mock.name).toEqual('mock');

// Creating a mock on the setter.
MockHelper.mockService(mock, 'name', 'set');
spyOnProperty(mock, 'name', 'set');
mock.name = 'mock';
expect(MockHelper.mockService(mock, 'name', 'set')).toHaveBeenCalledWith('mock');

// Creating a mock on the method.
MockHelper.mockService<jasmine.Spy>(mock, 'nameMethod').and.returnValue('mock');
spyOn(mock, 'nameMethod').and.returnValue('mock');
expect(mock.nameMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'nameMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the method that doesn't exist.
MockHelper.mockService<jasmine.Spy>(mock, 'fakeMethod').and.returnValue('mock');
MockHelper.mockService(mock, 'fakeMethod');
spyOn(mock as any, 'fakeMethod').and.returnValue('mock');
expect((mock as any).fakeMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'fakeMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the property that doesn't exist.
MockHelper.mockService(mock, 'fakeProp', 'get');
MockHelper.mockService(mock, 'fakeProp', 'set');
spyOnProperty(mock as any, 'fakeProp', 'get').and.returnValue('mockProp');
spyOnProperty(mock as any, 'fakeProp', 'set');
expect((mock as any).fakeProp).toEqual('mockProp');
(mock as any).fakeProp = 'mockPropSet';
expect(MockHelper.mockService(mock as any, 'fakeProp', 'set')).toHaveBeenCalledWith('mockPropSet');
});
});
```
Expand Down
2 changes: 1 addition & 1 deletion jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { mockServiceHelper } from './lib/mock-service';

declare const jest: any;

mockServiceHelper.registerMockFunction(() => jest.fn());
mockServiceHelper.registerMockFunction(name => jest.fn().mockName(name));
144 changes: 130 additions & 14 deletions lib/common/Mock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { MockDirective } from '../mock-directive';
import { MockModule } from '../mock-module';
import { MockPipe } from '../mock-pipe';

import { Mock, MockControlValueAccessor } from './Mock';

class ParentClass {
protected parentValue = true;

Expand All @@ -17,17 +15,60 @@ class ParentClass {
}
}

@NgModule({})
@NgModule({
providers: [
{
provide: 'MOCK',
useValue: 'HELLO',
},
],
})
class ChildModuleClass extends ParentClass implements PipeTransform {
protected childValue = true;

public childMethod(): boolean {
return this.childValue;
}

transform(): string {
return typeof this.childValue;
}
}

@Component({
template: '',
})
class ChildComponentClass extends ParentClass implements PipeTransform {
protected childValue = true;

public childMethod(): boolean {
return this.childValue;
}

transform(): string {
return typeof this.childValue;
}
}

@Directive({
selector: 'mock',
})
class ChildDirectiveClass extends ParentClass implements PipeTransform {
protected childValue = true;

public childMethod(): boolean {
return this.childValue;
}

transform(): string {
return typeof this.childValue;
}
}

@Pipe({
name: 'mock',
})
class ChildClass extends ParentClass implements PipeTransform {
class ChildPipeClass extends ParentClass implements PipeTransform {
protected childValue = true;

public childMethod(): boolean {
Expand All @@ -41,32 +82,107 @@ class ChildClass extends ParentClass implements PipeTransform {

describe('Mock', () => {
it('should affect as MockModule', () => {
const instance = new (MockModule(ChildClass))();
expect(instance).toEqual(jasmine.any(Mock));
const instance = new (MockModule(ChildModuleClass))();
expect(instance).toEqual(jasmine.any(ChildModuleClass));
expect((instance as any).__ngMocksMock).toEqual(true);
expect((instance as any).__ngMocksMockControlValueAccessor).toEqual(undefined);
expect(instance.parentMethod()).toBeUndefined('mocked to an empty function');
expect(instance.childMethod()).toBeUndefined('mocked to an empty function');
});

it('should affect as MockComponent', () => {
const instance = new (MockComponent(ChildClass))();
expect(instance).toEqual(jasmine.any(MockControlValueAccessor));
expect(instance).toEqual(jasmine.any(Mock));
const instance = new (MockComponent(ChildComponentClass))();
expect(instance).toEqual(jasmine.any(ChildComponentClass));
expect((instance as any).__ngMocksMock).toEqual(true);
expect((instance as any).__ngMocksMockControlValueAccessor).toEqual(true);

const spy = jasmine.createSpy('spy');
instance.registerOnChange(spy);
instance.__simulateChange('test');
expect(spy).toHaveBeenCalledWith('test');

expect(instance.parentMethod()).toBeUndefined('mocked to an empty function');
expect(instance.childMethod()).toBeUndefined('mocked to an empty function');
});

it('should affect as MockDirective', () => {
const instance = new (MockDirective(ChildClass))();
expect(instance).toEqual(jasmine.any(MockControlValueAccessor));
expect(instance).toEqual(jasmine.any(Mock));
const instance = new (MockDirective(ChildDirectiveClass))();
expect(instance).toEqual(jasmine.any(ChildDirectiveClass));
expect((instance as any).__ngMocksMock).toEqual(true);
expect((instance as any).__ngMocksMockControlValueAccessor).toEqual(true);

const spy = jasmine.createSpy('spy');
instance.registerOnChange(spy);
instance.__simulateChange('test');
expect(spy).toHaveBeenCalledWith('test');

expect(instance.parentMethod()).toBeUndefined('mocked to an empty function');
expect(instance.childMethod()).toBeUndefined('mocked to an empty function');
});

it('should affect as MockPipe', () => {
const instance = new (MockPipe(ChildClass))();
expect(instance).toEqual(jasmine.any(Mock));
const instance = new (MockPipe(ChildPipeClass))();
expect(instance).toEqual(jasmine.any(ChildPipeClass));
expect((instance as any).__ngMocksMock).toEqual(true);
expect((instance as any).__ngMocksMockControlValueAccessor).toEqual(undefined);
expect(instance.parentMethod()).toBeUndefined('mocked to an empty function');
expect(instance.childMethod()).toBeUndefined('mocked to an empty function');
});
});

describe('Mock prototype', () => {
@Component({
selector: 'custom',
template: '',
})
class CustomComponent {
public test = 'custom';

public get __ngMocksMock(): string {
return 'IMPOSSIBLE_OVERRIDE';
}

public get test1(): string {
return 'test1';
}

public set test2(value: string) {
this.test = value;
}

public testMethod(): string {
return this.test;
}
}

it('should get all things mocked and in the same time respect prototype', () => {
const mockDef = MockComponent(CustomComponent);
const mock = new mockDef();
expect(mock).toEqual(jasmine.any(CustomComponent));

// checking that it was processed through Mock
expect(mock.__ngMocksMock as any).toBe(true);
expect(mock.__ngMocksMockControlValueAccessor as any).toBe(true);

// checking that it was processed through MockControlValueAccessor
const spy = jasmine.createSpy('spy');
mock.registerOnChange(spy);
mock.__simulateChange('test');
expect(spy).toHaveBeenCalledWith('test');

// properties are mocked too
expect(mock.test1).toBeUndefined();
(mock as any).test1 = 'MyCustomValue';
expect(mock.test1).toEqual('MyCustomValue');

// properties are mocked too
expect(mock.test2).toBeUndefined();
(mock as any).test2 = 'MyCustomValue';
expect(mock.test2).toEqual('MyCustomValue');

// properties are mocked too
expect(mock.test).toBeUndefined();
(mock as any).test = 'MyCustomValue';
expect(mock.test).toEqual('MyCustomValue');
});
});
49 changes: 43 additions & 6 deletions lib/common/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,59 @@ import { mockServiceHelper } from '../mock-service';
// tslint:disable-next-line:no-unnecessary-class
export class Mock {
constructor() {
for (const method of (this as any).__mockedMethods) {
if ((this as any)[method]) {
// setting outputs
for (const output of (this as any).__mockedOutputs) {
if ((this as any)[output] || Object.getOwnPropertyDescriptor(this, output)) {
continue;
}
(this as any)[method] = mockServiceHelper.mockFunction();
(this as any)[output] = new EventEmitter<any>();
}
for (const output of (this as any).__mockedOutputs) {
if ((this as any)[output]) {

// setting our mocked methods and props
const prototype = Object.getPrototypeOf(this);
for (const method of mockServiceHelper.extractMethodsFromPrototype(prototype)) {
const descriptor = mockServiceHelper.extractPropertyDescriptor(prototype, method);
if (descriptor) {
Object.defineProperty(this, method, descriptor);
}
}
for (const prop of mockServiceHelper.extractPropertiesFromPrototype(prototype)) {
const descriptor = mockServiceHelper.extractPropertyDescriptor(prototype, prop);
if (!descriptor) {
continue;
}
(this as any)[output] = new EventEmitter<any>();
Object.defineProperty(this, prop, descriptor);
}

// setting mocks for original class methods and props
for (const method of mockServiceHelper.extractMethodsFromPrototype((this.constructor as any).mockOf.prototype)) {
if ((this as any)[method] || Object.getOwnPropertyDescriptor(this, method)) {
continue;
}
mockServiceHelper.mock(this, method);
}
for (const prop of mockServiceHelper.extractPropertiesFromPrototype((this.constructor as any).mockOf.prototype)) {
if ((this as any)[prop] || Object.getOwnPropertyDescriptor(this, prop)) {
continue;
}
mockServiceHelper.mock(this, prop, 'get');
mockServiceHelper.mock(this, prop, 'set');
}

// and faking prototype
Object.setPrototypeOf(this, (this.constructor as any).mockOf.prototype);
}

get __ngMocksMock(): boolean {
return true;
}
}

export class MockControlValueAccessor extends Mock implements ControlValueAccessor {
get __ngMocksMockControlValueAccessor(): boolean {
return true;
}

__simulateChange = (param: any) => {}; // tslint:disable-line:variable-name

__simulateTouch = () => {}; // tslint:disable-line:variable-name
Expand Down
4 changes: 0 additions & 4 deletions lib/common/mock-of.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Type } from '@angular/core';

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

// This helps with debugging in the browser. Decorating mock classes with this
// will change the display-name of the class to 'MockOf-<ClassName>` so our
// debugging output (and Angular's error messages) will mention our mock classes
Expand All @@ -16,8 +14,6 @@ export const MockOf = (mockClass: Type<any>, outputs?: string[]) => (constructor
nameConstructor: { value: constructor.name },
});

constructor.prototype.__mockedMethods = mockServiceHelper.extractMethodsFromPrototype(mockClass.prototype);

const mockedOutputs = [];
for (const output of outputs || []) {
mockedOutputs.push(output.split(':')[0]);
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-pipe/mock-pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function MockPipe<TPipe extends PipeTransform>(
transform = transform || defaultTransform;
}

const mockedPipe: Type<TPipe> = Pipe(options)(PipeMock as any);
const mockedPipe: Type<MockedPipe<TPipe>> = Pipe(options)(PipeMock as any);

return mockedPipe;
}
18 changes: 14 additions & 4 deletions lib/mock-service/mock-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,23 +200,33 @@ describe('MockService', () => {
expect(mock).toBeDefined();

// Creating a mock on the getter.
MockHelper.mockService<jasmine.Spy>(mock, 'name', 'get').and.returnValue('mock');
spyOnProperty(mock, 'name', 'get').and.returnValue('mock');
expect(mock.name).toEqual('mock');

// Creating a mock on the setter.
MockHelper.mockService(mock, 'name', 'set');
spyOnProperty(mock, 'name', 'set');
mock.name = 'mock';
expect(MockHelper.mockService(mock, 'name', 'set')).toHaveBeenCalledWith('mock');

// Creating a mock on the method.
MockHelper.mockService<jasmine.Spy>(mock, 'nameMethod').and.returnValue('mock');
spyOn(mock, 'nameMethod').and.returnValue('mock');
expect(mock.nameMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'nameMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the method that doesn't exist.
MockHelper.mockService<jasmine.Spy>(mock, 'fakeMethod').and.returnValue('mock');
MockHelper.mockService(mock, 'fakeMethod');
spyOn(mock as any, 'fakeMethod').and.returnValue('mock');
expect((mock as any).fakeMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'fakeMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the property that doesn't exist.
MockHelper.mockService(mock, 'fakeProp', 'get');
MockHelper.mockService(mock, 'fakeProp', 'set');
spyOnProperty(mock as any, 'fakeProp', 'get').and.returnValue('mockProp');
spyOnProperty(mock as any, 'fakeProp', 'set');
expect((mock as any).fakeProp).toEqual('mockProp');
(mock as any).fakeProp = 'mockPropSet';
expect(MockHelper.mockService(mock as any, 'fakeProp', 'set')).toHaveBeenCalledWith('mockPropSet');
});

it('mocks injection tokens as undefined', () => {
Expand Down
Loading

0 comments on commit 05dd90b

Please sign in to comment.