From 3b033243b86f7d287e6a751556b24022343eb699 Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Mon, 26 Jun 2017 10:58:31 -0700 Subject: [PATCH 01/14] expose all spies (including get/set) on a facade --- src/lib/index.spec.ts | 64 +++++++++++++++++++----- src/lib/index.ts | 111 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 149 insertions(+), 26 deletions(-) diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index b3e27b8..867895b 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------- -import { MockFactory } from './index'; +import { MockFactory, Mock } from './index'; interface IClass1 { publicProperty1: string; @@ -17,7 +17,6 @@ interface IClass1 { class Class1 implements IClass1 { private privateProperty1: string; private privateProperty2 = 'value-a1'; - private settablePropertyInternal = ''; public get gettableProperty1() { return 'value-c1'; @@ -27,6 +26,14 @@ class Class1 implements IClass1 { // noop } + public get getSetProperty1() { + return 'value-d1'; + } + + public set getSetProperty1(value: string) { + // noop + } + public publicMethod(arg1: string): number { return 42; } @@ -44,7 +51,7 @@ class Class1 implements IClass1 { class Class2 extends Class1 { } describe('MockFactory', () => { - let mockClass1Instance: IClass1; + let mockClass1Instance: Mock; describe('mocking a class using a typescript class', () => { @@ -87,6 +94,7 @@ describe('MockFactory', () => { expect(mockClass1Instance.publicProperty1).toBeUndefined(); expect(mockClass1Instance.publicProperty2).toBeUndefined(); expect(mockClass1Instance.gettableProperty1).toBeUndefined(); + expect(mockClass1Instance.getSetProperty1).toBeUndefined(); expect((mockClass1Instance as any).privateProperty1).toBeUndefined(); expect((mockClass1Instance as any).privateProperty2).toBeUndefined(); expect((mockClass1Instance as any).nonExistProperty).toBeUndefined(); @@ -99,12 +107,18 @@ describe('MockFactory', () => { expect(mockClass1Instance.publicProperty1).toBe('new-value-2'); expect(mockClass1Instance.publicProperty1).toBe('new-value-2'); - (mockClass1Instance as any).gettableProperty1 = 'new-value-1'; + mockClass1Instance._getSpy('gettableProperty1')._get.and.returnValue('new-value-1'); expect(mockClass1Instance.gettableProperty1).toBe('new-value-1'); - (mockClass1Instance as any).gettableProperty1 = 'new-value-2'; + mockClass1Instance._getSpy('gettableProperty1')._get.and.returnValue('new-value-2'); expect(mockClass1Instance.gettableProperty1).toBe('new-value-2'); expect(mockClass1Instance.gettableProperty1).toBe('new-value-2'); + mockClass1Instance._getSpy('getSetProperty1')._get.and.returnValue('new-value-1'); + expect(mockClass1Instance.getSetProperty1).toBe('new-value-1'); + mockClass1Instance._getSpy('getSetProperty1')._get.and.returnValue('new-value-2'); + expect(mockClass1Instance.getSetProperty1).toBe('new-value-2'); + expect(mockClass1Instance.getSetProperty1).toBe('new-value-2'); + (mockClass1Instance as any).privateProperty1 = 'new-value-1'; expect((mockClass1Instance as any).privateProperty1).toBe('new-value-1'); (mockClass1Instance as any).privateProperty1 = 'new-value-2'; @@ -137,6 +151,17 @@ describe('MockFactory', () => { (mockClass1Instance as any).privateMethod('value-2'); expect((mockClass1Instance as any).privateMethod).toHaveBeenCalledWith('value-2'); expect((mockClass1Instance as any).privateMethod).toHaveBeenCalledTimes(2); + + mockClass1Instance.getSetProperty1 = 'value-1'; + expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledWith('value-1'); + mockClass1Instance.getSetProperty1 = 'value-2'; + expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledWith('value-2'); + expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledTimes(2); + + const whatever1 = mockClass1Instance.getSetProperty1; + expect(mockClass1Instance._getSpy('getSetProperty1')._get).toHaveBeenCalled(); + const whatever2 = mockClass1Instance.getSetProperty1; + expect(mockClass1Instance._getSpy('getSetProperty1')._get).toHaveBeenCalledTimes(2); }); it('should allow spy setup before its first call', () => { @@ -171,17 +196,34 @@ describe('MockFactory', () => { expect((mockClass1Instance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); }); - it('should not allow spy to be replaced before its first call', () => { - expect(() => mockClass1Instance.publicMethod = jasmine.createSpy('newSpy')).toThrowError(); - expect(() => (mockClass1Instance as any).privateMethod = jasmine.createSpy('newSpy')).toThrowError(); + it('should allow spy to be replaced before its first call', () => { + const newSpy1 = jasmine.createSpy('newSpy') + expect(() => mockClass1Instance.publicMethod = newSpy1).not.toThrowError(); + mockClass1Instance.publicMethod('value-1'); + + expect(newSpy1).toHaveBeenCalledWith('value-1'); + + const newSpy2 = jasmine.createSpy('newSpy') + expect(() => (mockClass1Instance as any).privateMethod = newSpy2).not.toThrowError(); + (mockClass1Instance as any).privateMethod('value-1'); + + expect(newSpy2).toHaveBeenCalledWith('value-1'); }); - it('should not allow spy to be replaced after its first call', () => { + it('should allow spy to be replaced after its first call', () => { mockClass1Instance.publicMethod('whatever'); - expect(() => mockClass1Instance.publicMethod = jasmine.createSpy('newSpy')).toThrowError(); + const newSpy1 = jasmine.createSpy('newSpy') + expect(() => mockClass1Instance.publicMethod = newSpy1).not.toThrowError(); + mockClass1Instance.publicMethod('value-1'); + + expect(newSpy1).toHaveBeenCalledWith('value-1'); (mockClass1Instance as any).privateMethod('whatever'); - expect(() => (mockClass1Instance as any).privateMethod = jasmine.createSpy('newSpy')).toThrowError(); + const newSpy2 = jasmine.createSpy('newSpy') + expect(() => (mockClass1Instance as any).privateMethod = newSpy2).not.toThrowError(); + (mockClass1Instance as any).privateMethod('value-1'); + + expect(newSpy2).toHaveBeenCalledWith('value-1'); }); } }); diff --git a/src/lib/index.ts b/src/lib/index.ts index 65fdba9..27295e7 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,19 @@ +export declare type Mock = T & SpyFacade; + +export interface SpyFacade { + _getSpy(propertyName: keyof T): SpiedMember; +} + +export declare type Spied = { + [K in keyof T]: SpiedMember; +} + +export interface SpiedMember { + _func: jasmine.Spy; + _get: jasmine.Spy; + _set: jasmine.Spy; +} + interface Type extends Function { new (...args: any[]): T; } @@ -11,42 +27,106 @@ interface ValueMap { } class DynamicMockBase { + public spyFacade = Object.create(null); + private stub = Object.create(null); + private spyMap: SpyMap = Object.create(null); private valueMap: ValueMap = Object.create(null); public handler = { get: (target: T, propertyName: keyof T, receiver) => { - // trying to get a property, return value from valueMap. - if (typeof this.prototype[propertyName] !== 'function') { - return this.valueMap[propertyName]; - } - // trying to get a function, if we haven't created the spy, create one - if (!this.spyMap[propertyName]) { - const spy = jasmine.createSpy(propertyName); - this.spyMap[propertyName] = spy; - } + if (propertyName === '_getSpy') { + return (propertyNameParam) => this.getSpy(propertyNameParam); + } + + this.initSpy(propertyName); - return this.spyMap[propertyName]; + + return this.stub[propertyName]; }, // store whatever user wants in the value map set: (target, propertyName: keyof T, value, receiver) => { - if (typeof this.prototype[propertyName] === 'function') { - throw Error(`Assignment not allowed because ${propertyName} is already a spied function`); + + if (propertyName === '_getSpy') { + throw Error('Cannot modify _getSpy. It is part of the MockFactory'); } - this.valueMap[propertyName] = value; + this.initSpy(propertyName); + + this.stub[propertyName] = value; + return true; }, }; constructor(private prototype: T) {} + + public getSpy(propertyName: keyof T): SpiedMember { + this.initSpy(propertyName); + + return this.spyFacade[propertyName]; + } + + private initSpy(propertyName: keyof T): void { + // create spy if needed + if (!this.spyFacade[propertyName]) { + // if target is property + if (typeof this.prototype[propertyName] !== 'function') { + // TODO __lookupGetter__ and __lookupSetter will be deprecated but Object.getPropertyDesriptor has not arrived. + // Consider using polyfill to be future proof + const hasGetter = !!(this.prototype as any).__lookupGetter__(propertyName); // this will lookup inherited getter/setter + const hasSetter = !!(this.prototype as any).__lookupSetter__(propertyName); + let descriptor = Object.getOwnPropertyDescriptor(this.prototype, propertyName); // this will return undefined on inherited getter/setter + if (!descriptor) { + descriptor = { + value: undefined, + writable: true, + enumerable: true, + configurable: true, + }; + } else { + descriptor.value = undefined; + } + + if (hasGetter) { + descriptor.get = () => {}; + delete descriptor.value; + delete descriptor.writable; + } + + if (hasSetter) { + descriptor.set = (...arg) => {}; + delete descriptor.value; + delete descriptor.writable; + } + + Object.defineProperty(this.stub, propertyName, descriptor); + + this.spyFacade[propertyName] = { + _func: undefined, + _value: undefined, + _get: hasGetter || (descriptor && descriptor.get) ? spyOnProperty(this.stub, propertyName, 'get') : undefined, + _set: hasSetter || (descriptor && descriptor.set) ? spyOnProperty(this.stub, propertyName, 'set') : undefined, + } + // if target is function + } else { + const spy = jasmine.createSpy(propertyName); + this.stub[propertyName] = spy; + this.spyFacade[propertyName] = { + _func: spy, + _get: undefined, + _set: undefined, + }; + } + } + } } export class MockFactory { /** * create a mock object that has the identical interface with the class you passed in */ - public static create(blueprint: Type | T) { + public static create(blueprint: Type | T): Mock { let prototype: T; if (blueprint['prototype']) { prototype = blueprint['prototype']; @@ -55,6 +135,7 @@ export class MockFactory { } const mockBase = new DynamicMockBase(prototype); - return new Proxy(mockBase as any as T, mockBase.handler); + const proxy = new Proxy>(mockBase as any as Mock, mockBase.handler); + return proxy; } } From 84f1de456ea726fa8a37b0429886047ec63eba91 Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Wed, 28 Jun 2017 09:13:14 -0700 Subject: [PATCH 02/14] clean up interface --- src/lib/index.spec.ts | 268 +++++++++++++++++++++++++++--------------- src/lib/index.ts | 93 +++++++-------- 2 files changed, 215 insertions(+), 146 deletions(-) diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index 867895b..4c56a59 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -10,6 +10,7 @@ interface IClass1 { publicProperty1: string; publicProperty2: string; readonly gettableProperty1: string; + getSetProperty1: string; settableProperty1: string; publicMethod(arg1: string): number; } @@ -51,12 +52,12 @@ class Class1 implements IClass1 { class Class2 extends Class1 { } describe('MockFactory', () => { - let mockClass1Instance: Mock; + let mockInstance: Mock; describe('mocking a class using a typescript class', () => { beforeEach(() => { - mockClass1Instance = MockFactory.create(Class1); + mockInstance = MockFactory.create(Class1); }); runSharedSpecs(); @@ -65,7 +66,7 @@ describe('MockFactory', () => { describe('mocking an inherited class using an inherited typescript class', () => { beforeEach(() => { - mockClass1Instance = MockFactory.create(Class2); + mockInstance = MockFactory.create(Class2); }); runSharedSpecs(); @@ -74,7 +75,7 @@ describe('MockFactory', () => { describe('mocking an interface using an instance', () => { beforeEach(() => { const realInstance = new Class1('value-1'); - mockClass1Instance = MockFactory.create(realInstance); + mockInstance = MockFactory.create(realInstance); }); runSharedSpecs(); @@ -83,147 +84,222 @@ describe('MockFactory', () => { describe('mocking an inherited interface using an inherited instance', () => { beforeEach(() => { const realInstance = new Class2('value-1'); - mockClass1Instance = MockFactory.create(realInstance); + mockInstance = MockFactory.create(realInstance); }); runSharedSpecs(); }); + describe('mocking window object', () => { + let mockWindow: Mock; + beforeEach(() => { + mockWindow = MockFactory.create(window); + }); + + it('should mock functions on window', () => { + expect(mockWindow._spy.open._func).not.toHaveBeenCalled(); + expect(mockWindow.open).not.toHaveBeenCalled(); + mockWindow.open('https://foobar.com'); + expect(mockWindow._spy.open._func).toHaveBeenCalledWith('https://foobar.com'); + expect(mockWindow.open).toHaveBeenCalledWith('https://foobar.com'); + }); + + it('should spy getter on window', () => { + mockWindow._spy.scrollY._get.and.returnValue(42); + expect(mockWindow.scrollY).toBe(42); + }); + + it('should spy setter on window', () => { + mockWindow.name = 'foobar'; + expect(mockWindow._spy.name._set).toHaveBeenCalledWith('foobar'); + }); + }); + + describe('mocking location object', () => { + let mockLocation: Mock; + beforeEach(() => { + mockLocation = MockFactory.create(location); + }); + + it('should mock functions on location', () => { + expect(mockLocation._spy.replace._func).not.toHaveBeenCalled(); + expect(mockLocation.replace).not.toHaveBeenCalled(); + mockLocation.replace('https://foobar.com'); + expect(mockLocation._spy.replace._func).toHaveBeenCalledWith('https://foobar.com'); + expect(mockLocation.replace).toHaveBeenCalledWith('https://foobar.com'); + }); + + it('should spy getter on window', () => { + mockLocation._spy.origin._get.and.returnValue('https://foobar.com'); + expect(mockLocation.origin).toBe('https://foobar.com'); + }); + + it('should spy setter on window', () => { + mockLocation.host = 'foobar'; + expect(mockLocation._spy.host._set).toHaveBeenCalledWith('foobar'); + }); + }); + + describe('mocking localStorage object', () => { + let mockLocalStorage: Mock; + beforeEach(() => { + mockLocalStorage = MockFactory.create(localStorage); + }); + + it('should mock functions on location', () => { + expect(mockLocalStorage._spy.getItem._func).not.toHaveBeenCalled(); + expect(mockLocalStorage.getItem).not.toHaveBeenCalled(); + mockLocalStorage.getItem('foobar'); + expect(mockLocalStorage._spy.getItem._func).toHaveBeenCalledWith('foobar'); + expect(mockLocalStorage.getItem).toHaveBeenCalledWith('foobar'); + }); + + it('should spy getter on window', () => { + mockLocalStorage._spy.length._get.and.returnValue(42); + expect(mockLocalStorage.length).toBe(42); + }); + }); + function runSharedSpecs() { it('should return undefined for all properties', () => { - expect(mockClass1Instance.publicProperty1).toBeUndefined(); - expect(mockClass1Instance.publicProperty2).toBeUndefined(); - expect(mockClass1Instance.gettableProperty1).toBeUndefined(); - expect(mockClass1Instance.getSetProperty1).toBeUndefined(); - expect((mockClass1Instance as any).privateProperty1).toBeUndefined(); - expect((mockClass1Instance as any).privateProperty2).toBeUndefined(); - expect((mockClass1Instance as any).nonExistProperty).toBeUndefined(); + expect(mockInstance.publicProperty1).toBeUndefined(); + expect(mockInstance.publicProperty2).toBeUndefined(); + expect(mockInstance.gettableProperty1).toBeUndefined(); + expect(mockInstance.getSetProperty1).toBeUndefined(); + expect((mockInstance as any).privateProperty1).toBeUndefined(); + expect((mockInstance as any).privateProperty2).toBeUndefined(); + expect((mockInstance as any).nonExistProperty).toBeUndefined(); }); it('should persist modification for all properties', () => { - mockClass1Instance.publicProperty1 = 'new-value-1'; - expect(mockClass1Instance.publicProperty1).toBe('new-value-1'); - mockClass1Instance.publicProperty1 = 'new-value-2'; - expect(mockClass1Instance.publicProperty1).toBe('new-value-2'); - expect(mockClass1Instance.publicProperty1).toBe('new-value-2'); - - mockClass1Instance._getSpy('gettableProperty1')._get.and.returnValue('new-value-1'); - expect(mockClass1Instance.gettableProperty1).toBe('new-value-1'); - mockClass1Instance._getSpy('gettableProperty1')._get.and.returnValue('new-value-2'); - expect(mockClass1Instance.gettableProperty1).toBe('new-value-2'); - expect(mockClass1Instance.gettableProperty1).toBe('new-value-2'); - - mockClass1Instance._getSpy('getSetProperty1')._get.and.returnValue('new-value-1'); - expect(mockClass1Instance.getSetProperty1).toBe('new-value-1'); - mockClass1Instance._getSpy('getSetProperty1')._get.and.returnValue('new-value-2'); - expect(mockClass1Instance.getSetProperty1).toBe('new-value-2'); - expect(mockClass1Instance.getSetProperty1).toBe('new-value-2'); - - (mockClass1Instance as any).privateProperty1 = 'new-value-1'; - expect((mockClass1Instance as any).privateProperty1).toBe('new-value-1'); - (mockClass1Instance as any).privateProperty1 = 'new-value-2'; - expect((mockClass1Instance as any).privateProperty1).toBe('new-value-2'); - expect((mockClass1Instance as any).privateProperty1).toBe('new-value-2'); - - (mockClass1Instance as any).nonExistProperty = 'new-value-1'; - expect((mockClass1Instance as any).nonExistProperty).toBe('new-value-1'); - (mockClass1Instance as any).nonExistProperty = 'new-value-2'; - expect((mockClass1Instance as any).nonExistProperty).toBe('new-value-2'); - expect((mockClass1Instance as any).nonExistProperty).toBe('new-value-2'); + mockInstance.publicProperty1 = 'new-value-1'; + expect(mockInstance.publicProperty1).toBe('new-value-1'); + mockInstance.publicProperty1 = 'new-value-2'; + expect(mockInstance.publicProperty1).toBe('new-value-2'); + expect(mockInstance.publicProperty1).toBe('new-value-2'); + + mockInstance._spy.gettableProperty1._get.and.returnValue('new-value-1'); + expect(mockInstance.gettableProperty1).toBe('new-value-1'); + mockInstance._spy.gettableProperty1._get.and.returnValue('new-value-2'); + expect(mockInstance.gettableProperty1).toBe('new-value-2'); + expect(mockInstance.gettableProperty1).toBe('new-value-2'); + + mockInstance._spy.getSetProperty1._get.and.returnValue('new-value-1'); + expect(mockInstance.getSetProperty1).toBe('new-value-1'); + mockInstance._spy.getSetProperty1._get.and.returnValue('new-value-2'); + expect(mockInstance.getSetProperty1).toBe('new-value-2'); + expect(mockInstance.getSetProperty1).toBe('new-value-2'); + + (mockInstance as any).privateProperty1 = 'new-value-1'; + expect((mockInstance as any).privateProperty1).toBe('new-value-1'); + (mockInstance as any).privateProperty1 = 'new-value-2'; + expect((mockInstance as any).privateProperty1).toBe('new-value-2'); + expect((mockInstance as any).privateProperty1).toBe('new-value-2'); + + (mockInstance as any).nonExistProperty = 'new-value-1'; + expect((mockInstance as any).nonExistProperty).toBe('new-value-1'); + (mockInstance as any).nonExistProperty = 'new-value-2'; + expect((mockInstance as any).nonExistProperty).toBe('new-value-2'); + expect((mockInstance as any).nonExistProperty).toBe('new-value-2'); }); it('should return spy for all functions', () => { - expect(mockClass1Instance.publicMethod).not.toHaveBeenCalled(); - expect(mockClass1Instance.publicMethod).not.toHaveBeenCalled(); - expect((mockClass1Instance as any).privateMethod).not.toHaveBeenCalled(); - expect((mockClass1Instance as any).privateMethod).not.toHaveBeenCalled(); + expect(mockInstance.publicMethod).not.toHaveBeenCalled(); + expect(mockInstance.publicMethod).not.toHaveBeenCalled(); + expect((mockInstance as any).privateMethod).not.toHaveBeenCalled(); + expect((mockInstance as any).privateMethod).not.toHaveBeenCalled(); }); it('should register calls on each spy', () => { - mockClass1Instance.publicMethod('value-1'); - expect(mockClass1Instance.publicMethod).toHaveBeenCalledWith('value-1'); - mockClass1Instance.publicMethod('value-2'); - expect(mockClass1Instance.publicMethod).toHaveBeenCalledWith('value-2'); - expect(mockClass1Instance.publicMethod).toHaveBeenCalledTimes(2); - - (mockClass1Instance as any).privateMethod('value-1'); - expect((mockClass1Instance as any).privateMethod).toHaveBeenCalledWith('value-1'); - (mockClass1Instance as any).privateMethod('value-2'); - expect((mockClass1Instance as any).privateMethod).toHaveBeenCalledWith('value-2'); - expect((mockClass1Instance as any).privateMethod).toHaveBeenCalledTimes(2); - - mockClass1Instance.getSetProperty1 = 'value-1'; - expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledWith('value-1'); - mockClass1Instance.getSetProperty1 = 'value-2'; - expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledWith('value-2'); - expect(mockClass1Instance._getSpy('getSetProperty1')._set).toHaveBeenCalledTimes(2); - - const whatever1 = mockClass1Instance.getSetProperty1; - expect(mockClass1Instance._getSpy('getSetProperty1')._get).toHaveBeenCalled(); - const whatever2 = mockClass1Instance.getSetProperty1; - expect(mockClass1Instance._getSpy('getSetProperty1')._get).toHaveBeenCalledTimes(2); + mockInstance.publicMethod('value-1'); + expect(mockInstance.publicMethod).toHaveBeenCalledWith('value-1'); + mockInstance.publicMethod('value-2'); + expect(mockInstance.publicMethod).toHaveBeenCalledWith('value-2'); + expect(mockInstance.publicMethod).toHaveBeenCalledTimes(2); + + (mockInstance as any).privateMethod('value-1'); + expect((mockInstance as any).privateMethod).toHaveBeenCalledWith('value-1'); + (mockInstance as any).privateMethod('value-2'); + expect((mockInstance as any).privateMethod).toHaveBeenCalledWith('value-2'); + expect((mockInstance as any).privateMethod).toHaveBeenCalledTimes(2); + + mockInstance.getSetProperty1 = 'value-1'; + expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledWith('value-1'); + mockInstance.getSetProperty1 = 'value-2'; + expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledWith('value-2'); + expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledTimes(2); + + const whatever1 = mockInstance.getSetProperty1; + expect(mockInstance._spy.getSetProperty1._get).toHaveBeenCalled(); + const whatever2 = mockInstance.getSetProperty1; + expect(mockInstance._spy.getSetProperty1._get).toHaveBeenCalledTimes(2); }); it('should allow spy setup before its first call', () => { - (mockClass1Instance.publicMethod as jasmine.Spy).and.returnValue(999); - expect(mockClass1Instance.publicMethod('whatever')).toBe(999); - expect(mockClass1Instance.publicMethod('whatever')).toBe(999); + (mockInstance.publicMethod as jasmine.Spy).and.returnValue(999); + expect(mockInstance.publicMethod('whatever')).toBe(999); + expect(mockInstance.publicMethod('whatever')).toBe(999); - ((mockClass1Instance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((mockClass1Instance as any).privateMethod('whatever')).toBe(999); - expect((mockClass1Instance as any).privateMethod('whatever')).toBe(999); + ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); + expect((mockInstance as any).privateMethod('whatever')).toBe(999); + expect((mockInstance as any).privateMethod('whatever')).toBe(999); }); it('should allow spy setup after its first call', () => { - mockClass1Instance.publicMethod('whatever'); + mockInstance.publicMethod('whatever'); - (mockClass1Instance.publicMethod as jasmine.Spy).and.returnValue(111); - expect(mockClass1Instance.publicMethod('whatever')).toBe(111); - (mockClass1Instance.publicMethod as jasmine.Spy).and.returnValue(999); - expect(mockClass1Instance.publicMethod('whatever')).toBe(999); + (mockInstance.publicMethod as jasmine.Spy).and.returnValue(111); + expect(mockInstance.publicMethod('whatever')).toBe(111); + (mockInstance.publicMethod as jasmine.Spy).and.returnValue(999); + expect(mockInstance.publicMethod('whatever')).toBe(999); - (mockClass1Instance as any).privateMethod('whatever'); + (mockInstance as any).privateMethod('whatever'); - ((mockClass1Instance as any).privateMethod as jasmine.Spy).and.returnValue(111); - expect((mockClass1Instance as any).privateMethod('whatever')).toBe(111); - ((mockClass1Instance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((mockClass1Instance as any).privateMethod('whatever')).toBe(999); + ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(111); + expect((mockInstance as any).privateMethod('whatever')).toBe(111); + ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); + expect((mockInstance as any).privateMethod('whatever')).toBe(999); }); it('should allow spy to be added with non-exist names', () => { - (mockClass1Instance as any).nonExistMethod = jasmine.createSpy('newSpy'); - (mockClass1Instance as any).nonExistMethod('value-1'); - expect((mockClass1Instance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); + (mockInstance as any).nonExistMethod = jasmine.createSpy('newSpy'); + (mockInstance as any).nonExistMethod('value-1'); + expect((mockInstance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); }); it('should allow spy to be replaced before its first call', () => { const newSpy1 = jasmine.createSpy('newSpy') - expect(() => mockClass1Instance.publicMethod = newSpy1).not.toThrowError(); - mockClass1Instance.publicMethod('value-1'); + expect(() => mockInstance.publicMethod = newSpy1).not.toThrowError(); + mockInstance.publicMethod('value-1'); expect(newSpy1).toHaveBeenCalledWith('value-1'); const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (mockClass1Instance as any).privateMethod = newSpy2).not.toThrowError(); - (mockClass1Instance as any).privateMethod('value-1'); + expect(() => (mockInstance as any).privateMethod = newSpy2).not.toThrowError(); + (mockInstance as any).privateMethod('value-1'); expect(newSpy2).toHaveBeenCalledWith('value-1'); }); it('should allow spy to be replaced after its first call', () => { - mockClass1Instance.publicMethod('whatever'); + mockInstance.publicMethod('whatever'); const newSpy1 = jasmine.createSpy('newSpy') - expect(() => mockClass1Instance.publicMethod = newSpy1).not.toThrowError(); - mockClass1Instance.publicMethod('value-1'); + expect(() => mockInstance.publicMethod = newSpy1).not.toThrowError(); + mockInstance.publicMethod('value-1'); expect(newSpy1).toHaveBeenCalledWith('value-1'); - (mockClass1Instance as any).privateMethod('whatever'); + (mockInstance as any).privateMethod('whatever'); const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (mockClass1Instance as any).privateMethod = newSpy2).not.toThrowError(); - (mockClass1Instance as any).privateMethod('value-1'); + expect(() => (mockInstance as any).privateMethod = newSpy2).not.toThrowError(); + (mockInstance as any).privateMethod('value-1'); expect(newSpy2).toHaveBeenCalledWith('value-1'); }); + + it('should throw when _spy facade is modified', () => { + expect(() => mockInstance._spy = 42 as any).toThrow(); + expect(() => mockInstance._spy.getSetProperty1 = {} as any).toThrow(); + }); } }); diff --git a/src/lib/index.ts b/src/lib/index.ts index 27295e7..05e0804 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,7 +1,7 @@ export declare type Mock = T & SpyFacade; export interface SpyFacade { - _getSpy(propertyName: keyof T): SpiedMember; + _spy: Spied; } export declare type Spied = { @@ -9,49 +9,38 @@ export declare type Spied = { } export interface SpiedMember { - _func: jasmine.Spy; - _get: jasmine.Spy; - _set: jasmine.Spy; + _func?: jasmine.Spy; + _get?: jasmine.Spy; + _set?: jasmine.Spy; } interface Type extends Function { new (...args: any[]): T; } -interface SpyMap { - [key: string]: jasmine.Spy; -} - -interface ValueMap { - [key: string]: any; -} - -class DynamicMockBase { - public spyFacade = Object.create(null); +class DynamicBase { + public stubProxy: Mock; private stub = Object.create(null); + private spyProxy: T; + private spy = Object.create(null); - private spyMap: SpyMap = Object.create(null); - private valueMap: ValueMap = Object.create(null); - public handler = { + // create a spy before it is directly read/written + private stubProxyHandler = { get: (target: T, propertyName: keyof T, receiver) => { - - if (propertyName === '_getSpy') { - return (propertyNameParam) => this.getSpy(propertyNameParam); + if (propertyName === '_spy') { + return this.spyProxy; } - this.initSpy(propertyName); - + this.ensureSpy(propertyName); return this.stub[propertyName]; }, - // store whatever user wants in the value map set: (target, propertyName: keyof T, value, receiver) => { - - if (propertyName === '_getSpy') { - throw Error('Cannot modify _getSpy. It is part of the MockFactory'); + if (propertyName === '_spy') { + throw Error('Cannot modify _spy. It is part of the MockFactory'); } - this.initSpy(propertyName); + this.ensureSpy(propertyName); this.stub[propertyName] = value; @@ -59,17 +48,26 @@ class DynamicMockBase { }, }; - constructor(private prototype: T) {} + // create a spy before it is read from the spyFacade + private spyProxyHanlder = { + get: (target: T, propertyName: keyof T, receiver) => { + this.ensureSpy(propertyName); - public getSpy(propertyName: keyof T): SpiedMember { - this.initSpy(propertyName); + return this.spy[propertyName]; + }, + set: (target, propertyName: keyof T, value, receiver) => { + throw Error('Cannot modify spies. They are part of the MockFactory'); + }, + } - return this.spyFacade[propertyName]; + constructor(private prototype: T) { + this.stubProxy = new Proxy>(Object.create(null) as any as Mock, this.stubProxyHandler); + this.spyProxy = new Proxy(Object.create(null), this.spyProxyHanlder); } - private initSpy(propertyName: keyof T): void { + private ensureSpy(propertyName: keyof T): void { // create spy if needed - if (!this.spyFacade[propertyName]) { + if (!this.spy[propertyName]) { // if target is property if (typeof this.prototype[propertyName] !== 'function') { // TODO __lookupGetter__ and __lookupSetter will be deprecated but Object.getPropertyDesriptor has not arrived. @@ -77,16 +75,12 @@ class DynamicMockBase { const hasGetter = !!(this.prototype as any).__lookupGetter__(propertyName); // this will lookup inherited getter/setter const hasSetter = !!(this.prototype as any).__lookupSetter__(propertyName); let descriptor = Object.getOwnPropertyDescriptor(this.prototype, propertyName); // this will return undefined on inherited getter/setter - if (!descriptor) { - descriptor = { - value: undefined, - writable: true, - enumerable: true, - configurable: true, - }; - } else { - descriptor.value = undefined; - } + descriptor = { + value: undefined, + writable: true, + enumerable: true, + configurable: true, // required by spyOnProperty + }; if (hasGetter) { descriptor.get = () => {}; @@ -102,9 +96,8 @@ class DynamicMockBase { Object.defineProperty(this.stub, propertyName, descriptor); - this.spyFacade[propertyName] = { + this.spy[propertyName] = { _func: undefined, - _value: undefined, _get: hasGetter || (descriptor && descriptor.get) ? spyOnProperty(this.stub, propertyName, 'get') : undefined, _set: hasSetter || (descriptor && descriptor.set) ? spyOnProperty(this.stub, propertyName, 'set') : undefined, } @@ -112,7 +105,7 @@ class DynamicMockBase { } else { const spy = jasmine.createSpy(propertyName); this.stub[propertyName] = spy; - this.spyFacade[propertyName] = { + this.spy[propertyName] = { _func: spy, _get: undefined, _set: undefined, @@ -124,18 +117,18 @@ class DynamicMockBase { export class MockFactory { /** - * create a mock object that has the identical interface with the class you passed in + * create a mock object that has the identical interface as the class you passed in */ public static create(blueprint: Type | T): Mock { let prototype: T; if (blueprint['prototype']) { + // get the prototype for a TypeScript class prototype = blueprint['prototype']; } else { prototype = blueprint as T; } - const mockBase = new DynamicMockBase(prototype); - const proxy = new Proxy>(mockBase as any as Mock, mockBase.handler); - return proxy; + const dynamicBase = new DynamicBase(prototype); + return dynamicBase.stubProxy; } } From 912d563bb14d6d9027ec29a5f8b935074d1f380e Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Wed, 28 Jun 2017 09:14:06 -0700 Subject: [PATCH 03/14] fix typo --- src/lib/index.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index 4c56a59..56a6655 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -129,12 +129,12 @@ describe('MockFactory', () => { expect(mockLocation.replace).toHaveBeenCalledWith('https://foobar.com'); }); - it('should spy getter on window', () => { + it('should spy getter on location', () => { mockLocation._spy.origin._get.and.returnValue('https://foobar.com'); expect(mockLocation.origin).toBe('https://foobar.com'); }); - it('should spy setter on window', () => { + it('should spy setter on location', () => { mockLocation.host = 'foobar'; expect(mockLocation._spy.host._set).toHaveBeenCalledWith('foobar'); }); @@ -146,7 +146,7 @@ describe('MockFactory', () => { mockLocalStorage = MockFactory.create(localStorage); }); - it('should mock functions on location', () => { + it('should mock functions on localStorage', () => { expect(mockLocalStorage._spy.getItem._func).not.toHaveBeenCalled(); expect(mockLocalStorage.getItem).not.toHaveBeenCalled(); mockLocalStorage.getItem('foobar'); @@ -154,7 +154,7 @@ describe('MockFactory', () => { expect(mockLocalStorage.getItem).toHaveBeenCalledWith('foobar'); }); - it('should spy getter on window', () => { + it('should spy getter on localStorage', () => { mockLocalStorage._spy.length._get.and.returnValue(42); expect(mockLocalStorage.length).toBe(42); }); From 8cd8e641f564f1f7ebd4115050aec8b83ee52cae Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Wed, 28 Jun 2017 22:57:16 -0700 Subject: [PATCH 04/14] add more tests for getter setters --- src/lib/index.spec.ts | 576 +++++++++++++++++++++++++++++++----------- 1 file changed, 433 insertions(+), 143 deletions(-) diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index 56a6655..adf886c 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -6,91 +6,161 @@ import { MockFactory, Mock } from './index'; -interface IClass1 { +interface IBaseClass { publicProperty1: string; publicProperty2: string; - readonly gettableProperty1: string; - getSetProperty1: string; - settableProperty1: string; - publicMethod(arg1: string): number; + readonly publicGetterProperty1: string; + publicSetterProperty1: string; + publicGetterSetterProperty1: string; + publicMethod1(arg1: string): number; } -class Class1 implements IClass1 { +interface ISubClass extends IBaseClass { + subPublicMethod1(arg1: string): number; +} + +class BaseClass implements IBaseClass { + public publicProperty1: string; + protected protectedProperty1: string; private privateProperty1: string; - private privateProperty2 = 'value-a1'; - public get gettableProperty1() { - return 'value-c1'; - } + public publicProperty2 = 'value-a1'; + protected protectedProperty2: 'value-b1' + private privateProperty2 = 'value-c1'; - public set settableProperty1(value: string) { - // noop - } + public get publicGetterProperty1() { return 'value-d1'; } + protected get protectedGetterProperty1() { return 'value-f1'; } + private get privateGetterProperty1() { return 'value-f1'; } - public get getSetProperty1() { - return 'value-d1'; - } + public set publicSetterProperty1(value: string) { } + protected set protectedSetterProperty1(value: string) { } + private set privateSetterProperty1(value: string) { } + + public get publicGetterSetterProperty1() { return 'value-e1'; } + protected get protectedGetterSetterProperty1() { return 'value-g1'; } + private get privateGetterSetterProperty1() { return 'value-g1'; } - public set getSetProperty1(value: string) { - // noop + public set publicGetterSetterProperty1(value: string) { } + protected set protectedGetterSetterProperty1(value: string) { } + private set privateGetterSetterProperty1(value: string) { } + + public publicMethod1(arg1: string): number { + return 2; } - public publicMethod(arg1: string): number { - return 42; + protected protectedMethod(arg1: string): number { + return 3; } private privateMethod(arg1: string): number { - return 123; + return 5; } +} - constructor( - public publicProperty1: string, - public publicProperty2 = 'value-b1', - ) {} +class SubClass extends BaseClass implements ISubClass { + public subPublicMethod1(arg1: string): number { + return 7; + } } -class Class2 extends Class1 { } +function createMockFromBaseClass(): Mock { + return MockFactory.create(BaseClass); +} -describe('MockFactory', () => { - let mockInstance: Mock; +function createMockFromSubClass(): Mock { + return MockFactory.create(SubClass); +} - describe('mocking a class using a typescript class', () => { +function createMockFromBaseIntance(): Mock { + const instance = new BaseClass(); + return MockFactory.create(instance); +} + +function createMockFromSubInstance(): Mock { + const instance = new SubClass(); + return MockFactory.create(instance); +} + +function createMockWindow(): Mock { + return MockFactory.create(window); +} + +function createMockLocation(): Mock { + return MockFactory.create(location); +} + +function createMockLocalStorage(): Mock { + return MockFactory.create(localStorage); +} + +describe('Creating mocks', () => { + it('should create a mock from a base class', () => { + expect(() => createMockFromBaseClass()).not.toThrow() + }); + + it('should create a mock from a sub class', () => { + expect(() => createMockFromSubClass()).not.toThrow() + }); + + it('should create a mock from a base instance', () => { + expect(() => createMockFromBaseIntance()).not.toThrow() + }); + + it('should create a mock from a sub instance', () => { + expect(() => createMockFromSubInstance()).not.toThrow() + }); + + it('should create a mock from the window object', () => { + expect(() => createMockWindow()).not.toThrow() + }); + + it('should create a mock from the location object', () => { + expect(() => createMockLocation()).not.toThrow() + }); + + it('should create a mock from the localStorage object', () => { + expect(() => createMockLocalStorage()).not.toThrow() + }); +}); + +describe('Using mocks', () => { + let commonInstance: Mock; + + describe('using a mock created from a base class', () => { beforeEach(() => { - mockInstance = MockFactory.create(Class1); + commonInstance = createMockFromBaseClass(); }); - runSharedSpecs(); + runCommonSpecs(); }); - describe('mocking an inherited class using an inherited typescript class', () => { + describe('using a mock created from a sub class', () => { beforeEach(() => { - mockInstance = MockFactory.create(Class2); + commonInstance = createMockFromSubClass(); }); - runSharedSpecs(); + runCommonSpecs(); }); - describe('mocking an interface using an instance', () => { + describe('using a mock created from an instance of a base class', () => { beforeEach(() => { - const realInstance = new Class1('value-1'); - mockInstance = MockFactory.create(realInstance); + commonInstance = createMockFromBaseIntance(); }); - runSharedSpecs(); + runCommonSpecs(); }); - describe('mocking an inherited interface using an inherited instance', () => { + describe('using a mock created from an instance of a sub class', () => { beforeEach(() => { - const realInstance = new Class2('value-1'); - mockInstance = MockFactory.create(realInstance); + commonInstance = createMockFromSubInstance(); }); - runSharedSpecs(); + runCommonSpecs(); }); - describe('mocking window object', () => { + describe('using a mock created from the window object', () => { let mockWindow: Mock; beforeEach(() => { mockWindow = MockFactory.create(window); @@ -115,7 +185,7 @@ describe('MockFactory', () => { }); }); - describe('mocking location object', () => { + describe('using a mock created from the location object', () => { let mockLocation: Mock; beforeEach(() => { mockLocation = MockFactory.create(location); @@ -132,6 +202,9 @@ describe('MockFactory', () => { it('should spy getter on location', () => { mockLocation._spy.origin._get.and.returnValue('https://foobar.com'); expect(mockLocation.origin).toBe('https://foobar.com'); + + mockLocation._spy.search._get.and.returnValue('?param=1'); + expect(mockLocation.search).toBe('?param=1'); }); it('should spy setter on location', () => { @@ -140,7 +213,7 @@ describe('MockFactory', () => { }); }); - describe('mocking localStorage object', () => { + describe('using a mock created from the localStorage object', () => { let mockLocalStorage: Mock; beforeEach(() => { mockLocalStorage = MockFactory.create(localStorage); @@ -160,146 +233,363 @@ describe('MockFactory', () => { }); }); - function runSharedSpecs() { - it('should return undefined for all properties', () => { - expect(mockInstance.publicProperty1).toBeUndefined(); - expect(mockInstance.publicProperty2).toBeUndefined(); - expect(mockInstance.gettableProperty1).toBeUndefined(); - expect(mockInstance.getSetProperty1).toBeUndefined(); - expect((mockInstance as any).privateProperty1).toBeUndefined(); - expect((mockInstance as any).privateProperty2).toBeUndefined(); - expect((mockInstance as any).nonExistProperty).toBeUndefined(); - }); - - it('should persist modification for all properties', () => { - mockInstance.publicProperty1 = 'new-value-1'; - expect(mockInstance.publicProperty1).toBe('new-value-1'); - mockInstance.publicProperty1 = 'new-value-2'; - expect(mockInstance.publicProperty1).toBe('new-value-2'); - expect(mockInstance.publicProperty1).toBe('new-value-2'); - - mockInstance._spy.gettableProperty1._get.and.returnValue('new-value-1'); - expect(mockInstance.gettableProperty1).toBe('new-value-1'); - mockInstance._spy.gettableProperty1._get.and.returnValue('new-value-2'); - expect(mockInstance.gettableProperty1).toBe('new-value-2'); - expect(mockInstance.gettableProperty1).toBe('new-value-2'); - - mockInstance._spy.getSetProperty1._get.and.returnValue('new-value-1'); - expect(mockInstance.getSetProperty1).toBe('new-value-1'); - mockInstance._spy.getSetProperty1._get.and.returnValue('new-value-2'); - expect(mockInstance.getSetProperty1).toBe('new-value-2'); - expect(mockInstance.getSetProperty1).toBe('new-value-2'); - - (mockInstance as any).privateProperty1 = 'new-value-1'; - expect((mockInstance as any).privateProperty1).toBe('new-value-1'); - (mockInstance as any).privateProperty1 = 'new-value-2'; - expect((mockInstance as any).privateProperty1).toBe('new-value-2'); - expect((mockInstance as any).privateProperty1).toBe('new-value-2'); - - (mockInstance as any).nonExistProperty = 'new-value-1'; - expect((mockInstance as any).nonExistProperty).toBe('new-value-1'); - (mockInstance as any).nonExistProperty = 'new-value-2'; - expect((mockInstance as any).nonExistProperty).toBe('new-value-2'); - expect((mockInstance as any).nonExistProperty).toBe('new-value-2'); + function runCommonSpecs() { + it('should return undefined for all public properties', () => { + expect(commonInstance.publicProperty1).toBeUndefined(); + expect(commonInstance.publicProperty2).toBeUndefined(); + expect(commonInstance.publicGetterProperty1).toBeUndefined(); + expect(commonInstance.publicSetterProperty1).toBeUndefined(); + expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['publicProperty1']).toBeUndefined(); + expect(commonInstance['publicProperty2']).toBeUndefined(); + expect(commonInstance['publicGetterProperty1']).toBeUndefined(); + expect(commonInstance['publicSetterProperty1']).toBeUndefined(); + expect(commonInstance['publicGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for all protected properties', () => { + expect((commonInstance as any).protectedProperty1).toBeUndefined(); + expect((commonInstance as any).protectedProperty2).toBeUndefined(); + expect((commonInstance as any).protectedGetterProperty1).toBeUndefined(); + expect((commonInstance as any).protectedSetterProperty1).toBeUndefined(); + expect((commonInstance as any).protectedGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['protectedProperty1']).toBeUndefined(); + expect(commonInstance['protectedProperty2']).toBeUndefined(); + expect(commonInstance['protectedGetterProperty1']).toBeUndefined(); + expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); + expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for all private properties', () => { + expect((commonInstance as any).privateProperty1).toBeUndefined(); + expect((commonInstance as any).privateProperty2).toBeUndefined(); + expect((commonInstance as any).privateGetterProperty1).toBeUndefined(); + expect((commonInstance as any).privateSetterProperty1).toBeUndefined(); + expect((commonInstance as any).privateGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['privateProperty1']).toBeUndefined(); + expect(commonInstance['privateProperty2']).toBeUndefined(); + expect(commonInstance['privateGetterProperty1']).toBeUndefined(); + expect(commonInstance['privateSetterProperty1']).toBeUndefined(); + expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for non-exist properties', () => { + expect((commonInstance as any).nonExistProperty).toBeUndefined(); + expect(commonInstance['nonExistProperty']).toBeUndefined(); + }); + + it('should persist the first modification on properties', () => { + commonInstance.publicProperty1 = 'new-value'; + expect(commonInstance.publicProperty1).toBe('new-value'); + + commonInstance['protectedProperty1'] = 'new-value'; + expect(commonInstance['protectedProperty1']).toBe('new-value'); + + commonInstance['privateProperty1'] = 'new-value'; + expect(commonInstance['privateProperty1']).toBe('new-value'); + + commonInstance['nonExistProperty'] = 'new-value'; + expect(commonInstance['nonExistProperty']).toBe('new-value'); + }); + + it('should persist all modifications on properties', () => { + commonInstance.publicProperty1 = 'new-value-1'; + commonInstance.publicProperty1 = 'new-value-2'; + expect(commonInstance.publicProperty1).toBe('new-value-2'); + + commonInstance['protectedProperty1'] = 'new-value-1'; + commonInstance['protectedProperty1'] = 'new-value-2'; + expect(commonInstance['protectedProperty1']).toBe('new-value-2'); + + commonInstance['privateProperty1'] = 'new-value-1'; + commonInstance['privateProperty1'] = 'new-value-2'; + expect(commonInstance['privateProperty1']).toBe('new-value-2'); + + commonInstance['nonExistProperty'] = 'new-value-1'; + commonInstance['nonExistProperty'] = 'new-value-2'; + expect(commonInstance['nonExistProperty']).toBe('new-value-2'); + }); + + it('should ignore the first direct modification on properties with setters', () => { + commonInstance.publicSetterProperty1 = 'new-value'; + expect(commonInstance.publicSetterProperty1).toBeUndefined(); + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); + + commonInstance['protectedSetterProperty1'] = 'new-value'; + expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); + + commonInstance['privateSetterProperty1'] = 'new-value'; + expect(commonInstance['privateSetterProperty1']).toBeUndefined(); + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); + }); + + it('should ignore all direct modifications on properties with setters', () => { + commonInstance.publicSetterProperty1 = 'new-value-1'; + commonInstance.publicSetterProperty1 = 'new-value-2'; + expect(commonInstance.publicSetterProperty1).toBeUndefined(); + commonInstance.publicGetterSetterProperty1 = 'new-value-1'; + commonInstance.publicGetterSetterProperty1 = 'new-value-2'; + expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); + + commonInstance['protectedSetterProperty1'] = 'new-value-1'; + commonInstance['protectedSetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); + commonInstance['protectedGetterSetterProperty1'] = 'new-value-1'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); + + commonInstance['privateSetterProperty1'] = 'new-value-1'; + commonInstance['privateSetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateSetterProperty1']).toBeUndefined(); + commonInstance['privateGetterSetterProperty1'] = 'new-value-1'; + commonInstance['privateGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); + }); + + it('should throw for modifications on properties with only getters', () => { + expect(() => (commonInstance as any).publicGetterProperty1 = 'new-value').toThrow(); + expect(() => (commonInstance as any).protectedGetterProperty1 = 'new-value').toThrow(); + expect(() => (commonInstance as any).privateGetterProperty1 = 'new-value').toThrow(); + }); + + it('should spy getters before they are used', () => { + expect(commonInstance._spy.publicGetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._get).not.toHaveBeenCalled(); + + expect(commonInstance._spy['protectedGetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterSetterProperty1']._get).not.toHaveBeenCalled(); + + expect(commonInstance._spy['privateGetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterSetterProperty1']._get).not.toHaveBeenCalled(); + }); + + it('should spy setters before they are used', () => { + expect(commonInstance._spy.publicSetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._set).not.toHaveBeenCalled(); + + expect(commonInstance._spy['protectedSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).not.toHaveBeenCalled(); + + expect(commonInstance._spy['privateSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).not.toHaveBeenCalled(); + }); + + it('should record calls on getters', () => { + let temp1 = commonInstance.publicGetterProperty1; + temp1 = commonInstance.publicGetterSetterProperty1; + + expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalled(); + + temp1 = commonInstance.publicGetterProperty1; + temp1 = commonInstance.publicGetterSetterProperty1; + + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalledTimes(2); + }); + + it('should record calls on setters', () => { + commonInstance.publicSetterProperty1 = 'new-value'; + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('new-value'); + commonInstance.publicSetterProperty1 = 'new-value'; + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledTimes(2); + + commonInstance['protectedSetterProperty1'] = 'new-value'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + commonInstance['protectedSetterProperty1'] = 'new-value'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + + commonInstance['privateSetterProperty1'] = 'new-value'; + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + commonInstance['privateSetterProperty1'] = 'new-value'; + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + }); + + it('should modify results from getters before they are used', () => { + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + }); + + it('should modify results from getters after they are used', () => { + let temp1 = commonInstance.publicGetterProperty1; + temp1 = commonInstance.publicGetterSetterProperty1; + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + + temp1 = commonInstance['protectedGetterProperty1']; + temp1 = commonInstance['protectedGetterSetterProperty1']; + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + + temp1 = commonInstance['privateGetterProperty1']; + temp1 = commonInstance['privateGetterSetterProperty1']; + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + }); + + it('should re-modify results from getters', () => { + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-2'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-2'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-2'); + + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-2'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-2'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-2'); + + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-2'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-2'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-2'); }); it('should return spy for all functions', () => { - expect(mockInstance.publicMethod).not.toHaveBeenCalled(); - expect(mockInstance.publicMethod).not.toHaveBeenCalled(); - expect((mockInstance as any).privateMethod).not.toHaveBeenCalled(); - expect((mockInstance as any).privateMethod).not.toHaveBeenCalled(); + expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); + expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); + expect((commonInstance as any).privateMethod).not.toHaveBeenCalled(); + expect((commonInstance as any).privateMethod).not.toHaveBeenCalled(); }); it('should register calls on each spy', () => { - mockInstance.publicMethod('value-1'); - expect(mockInstance.publicMethod).toHaveBeenCalledWith('value-1'); - mockInstance.publicMethod('value-2'); - expect(mockInstance.publicMethod).toHaveBeenCalledWith('value-2'); - expect(mockInstance.publicMethod).toHaveBeenCalledTimes(2); - - (mockInstance as any).privateMethod('value-1'); - expect((mockInstance as any).privateMethod).toHaveBeenCalledWith('value-1'); - (mockInstance as any).privateMethod('value-2'); - expect((mockInstance as any).privateMethod).toHaveBeenCalledWith('value-2'); - expect((mockInstance as any).privateMethod).toHaveBeenCalledTimes(2); - - mockInstance.getSetProperty1 = 'value-1'; - expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledWith('value-1'); - mockInstance.getSetProperty1 = 'value-2'; - expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledWith('value-2'); - expect(mockInstance._spy.getSetProperty1._set).toHaveBeenCalledTimes(2); - - const whatever1 = mockInstance.getSetProperty1; - expect(mockInstance._spy.getSetProperty1._get).toHaveBeenCalled(); - const whatever2 = mockInstance.getSetProperty1; - expect(mockInstance._spy.getSetProperty1._get).toHaveBeenCalledTimes(2); + commonInstance.publicMethod1('value-1'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-1'); + commonInstance.publicMethod1('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledTimes(2); + + (commonInstance as any).privateMethod('value-1'); + expect((commonInstance as any).privateMethod).toHaveBeenCalledWith('value-1'); + (commonInstance as any).privateMethod('value-2'); + expect((commonInstance as any).privateMethod).toHaveBeenCalledWith('value-2'); + expect((commonInstance as any).privateMethod).toHaveBeenCalledTimes(2); + + commonInstance.publicGetterSetterProperty1 = 'value-1'; + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('value-1'); + commonInstance.publicGetterSetterProperty1 = 'value-2'; + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('value-2'); + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledTimes(2); + + const whatever1 = commonInstance.publicGetterSetterProperty1; + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalled(); + const whatever2 = commonInstance.publicGetterSetterProperty1; + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalledTimes(2); }); it('should allow spy setup before its first call', () => { - (mockInstance.publicMethod as jasmine.Spy).and.returnValue(999); - expect(mockInstance.publicMethod('whatever')).toBe(999); - expect(mockInstance.publicMethod('whatever')).toBe(999); + (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); - ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((mockInstance as any).privateMethod('whatever')).toBe(999); - expect((mockInstance as any).privateMethod('whatever')).toBe(999); + ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).privateMethod('whatever')).toBe(999); + expect((commonInstance as any).privateMethod('whatever')).toBe(999); }); it('should allow spy setup after its first call', () => { - mockInstance.publicMethod('whatever'); + commonInstance.publicMethod1('whatever'); - (mockInstance.publicMethod as jasmine.Spy).and.returnValue(111); - expect(mockInstance.publicMethod('whatever')).toBe(111); - (mockInstance.publicMethod as jasmine.Spy).and.returnValue(999); - expect(mockInstance.publicMethod('whatever')).toBe(999); + (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(111); + expect(commonInstance.publicMethod1('whatever')).toBe(111); + (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); - (mockInstance as any).privateMethod('whatever'); + (commonInstance as any).privateMethod('whatever'); - ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(111); - expect((mockInstance as any).privateMethod('whatever')).toBe(111); - ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((mockInstance as any).privateMethod('whatever')).toBe(999); + ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(111); + expect((commonInstance as any).privateMethod('whatever')).toBe(111); + ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).privateMethod('whatever')).toBe(999); }); it('should allow spy to be added with non-exist names', () => { - (mockInstance as any).nonExistMethod = jasmine.createSpy('newSpy'); - (mockInstance as any).nonExistMethod('value-1'); - expect((mockInstance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); + (commonInstance as any).nonExistMethod = jasmine.createSpy('newSpy'); + (commonInstance as any).nonExistMethod('value-1'); + expect((commonInstance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); }); it('should allow spy to be replaced before its first call', () => { const newSpy1 = jasmine.createSpy('newSpy') - expect(() => mockInstance.publicMethod = newSpy1).not.toThrowError(); - mockInstance.publicMethod('value-1'); + expect(() => commonInstance.publicMethod1 = newSpy1).not.toThrowError(); + commonInstance.publicMethod1('value-1'); expect(newSpy1).toHaveBeenCalledWith('value-1'); const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (mockInstance as any).privateMethod = newSpy2).not.toThrowError(); - (mockInstance as any).privateMethod('value-1'); + expect(() => (commonInstance as any).privateMethod = newSpy2).not.toThrowError(); + (commonInstance as any).privateMethod('value-1'); expect(newSpy2).toHaveBeenCalledWith('value-1'); }); it('should allow spy to be replaced after its first call', () => { - mockInstance.publicMethod('whatever'); + commonInstance.publicMethod1('whatever'); const newSpy1 = jasmine.createSpy('newSpy') - expect(() => mockInstance.publicMethod = newSpy1).not.toThrowError(); - mockInstance.publicMethod('value-1'); + expect(() => commonInstance.publicMethod1 = newSpy1).not.toThrowError(); + commonInstance.publicMethod1('value-1'); expect(newSpy1).toHaveBeenCalledWith('value-1'); - (mockInstance as any).privateMethod('whatever'); + (commonInstance as any).privateMethod('whatever'); const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (mockInstance as any).privateMethod = newSpy2).not.toThrowError(); - (mockInstance as any).privateMethod('value-1'); + expect(() => (commonInstance as any).privateMethod = newSpy2).not.toThrowError(); + (commonInstance as any).privateMethod('value-1'); expect(newSpy2).toHaveBeenCalledWith('value-1'); }); it('should throw when _spy facade is modified', () => { - expect(() => mockInstance._spy = 42 as any).toThrow(); - expect(() => mockInstance._spy.getSetProperty1 = {} as any).toThrow(); + expect(() => commonInstance._spy = 42 as any).toThrow(); + expect(() => commonInstance._spy.publicGetterSetterProperty1 = {} as any).toThrow(); }); } }); From 518bcdf9a0949707a6f64da61ebe2d0e20a85067 Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Thu, 29 Jun 2017 21:57:31 -0700 Subject: [PATCH 05/14] all all properties to have getter setter spies --- src/lib/index.spec.ts | 1086 +++++++++++++++++++++++++++-------------- src/lib/index.ts | 56 ++- tslint.json | 2 +- 3 files changed, 755 insertions(+), 389 deletions(-) diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index adf886c..1018db9 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -48,11 +48,11 @@ class BaseClass implements IBaseClass { return 2; } - protected protectedMethod(arg1: string): number { + protected protectedMethod1(arg1: string): number { return 3; } - private privateMethod(arg1: string): number { + private privateMethod1(arg1: string): number { return 5; } } @@ -166,7 +166,7 @@ describe('Using mocks', () => { mockWindow = MockFactory.create(window); }); - it('should mock functions on window', () => { + it('should mock functions', () => { expect(mockWindow._spy.open._func).not.toHaveBeenCalled(); expect(mockWindow.open).not.toHaveBeenCalled(); mockWindow.open('https://foobar.com'); @@ -174,15 +174,20 @@ describe('Using mocks', () => { expect(mockWindow.open).toHaveBeenCalledWith('https://foobar.com'); }); - it('should spy getter on window', () => { + it('should spy getter', () => { mockWindow._spy.scrollY._get.and.returnValue(42); expect(mockWindow.scrollY).toBe(42); }); - it('should spy setter on window', () => { + it('should spy setter', () => { mockWindow.name = 'foobar'; expect(mockWindow._spy.name._set).toHaveBeenCalledWith('foobar'); }); + + it('should persist modification on properties', () => { + (mockWindow as any).document = 'foobar'; + expect(mockWindow.document).toBe('foobar'); + }); }); describe('using a mock created from the location object', () => { @@ -191,7 +196,7 @@ describe('Using mocks', () => { mockLocation = MockFactory.create(location); }); - it('should mock functions on location', () => { + it('should mock functions', () => { expect(mockLocation._spy.replace._func).not.toHaveBeenCalled(); expect(mockLocation.replace).not.toHaveBeenCalled(); mockLocation.replace('https://foobar.com'); @@ -199,7 +204,7 @@ describe('Using mocks', () => { expect(mockLocation.replace).toHaveBeenCalledWith('https://foobar.com'); }); - it('should spy getter on location', () => { + it('should spy getter', () => { mockLocation._spy.origin._get.and.returnValue('https://foobar.com'); expect(mockLocation.origin).toBe('https://foobar.com'); @@ -207,10 +212,15 @@ describe('Using mocks', () => { expect(mockLocation.search).toBe('?param=1'); }); - it('should spy setter on location', () => { + it('should spy setter', () => { mockLocation.host = 'foobar'; expect(mockLocation._spy.host._set).toHaveBeenCalledWith('foobar'); }); + + it('should persist modification on properties', () => { + mockLocation.search = '?foo=bar'; + expect(mockLocation.search).toBe('?foo=bar'); + }); }); describe('using a mock created from the localStorage object', () => { @@ -219,7 +229,7 @@ describe('Using mocks', () => { mockLocalStorage = MockFactory.create(localStorage); }); - it('should mock functions on localStorage', () => { + it('should mock functions', () => { expect(mockLocalStorage._spy.getItem._func).not.toHaveBeenCalled(); expect(mockLocalStorage.getItem).not.toHaveBeenCalled(); mockLocalStorage.getItem('foobar'); @@ -227,369 +237,717 @@ describe('Using mocks', () => { expect(mockLocalStorage.getItem).toHaveBeenCalledWith('foobar'); }); - it('should spy getter on localStorage', () => { + it('should spy getter', () => { mockLocalStorage._spy.length._get.and.returnValue(42); expect(mockLocalStorage.length).toBe(42); }); - }); - - function runCommonSpecs() { - it('should return undefined for all public properties', () => { - expect(commonInstance.publicProperty1).toBeUndefined(); - expect(commonInstance.publicProperty2).toBeUndefined(); - expect(commonInstance.publicGetterProperty1).toBeUndefined(); - expect(commonInstance.publicSetterProperty1).toBeUndefined(); - expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); - - expect(commonInstance['publicProperty1']).toBeUndefined(); - expect(commonInstance['publicProperty2']).toBeUndefined(); - expect(commonInstance['publicGetterProperty1']).toBeUndefined(); - expect(commonInstance['publicSetterProperty1']).toBeUndefined(); - expect(commonInstance['publicGetterSetterProperty1']).toBeUndefined(); - }); - - it('should return undefined for all protected properties', () => { - expect((commonInstance as any).protectedProperty1).toBeUndefined(); - expect((commonInstance as any).protectedProperty2).toBeUndefined(); - expect((commonInstance as any).protectedGetterProperty1).toBeUndefined(); - expect((commonInstance as any).protectedSetterProperty1).toBeUndefined(); - expect((commonInstance as any).protectedGetterSetterProperty1).toBeUndefined(); - - expect(commonInstance['protectedProperty1']).toBeUndefined(); - expect(commonInstance['protectedProperty2']).toBeUndefined(); - expect(commonInstance['protectedGetterProperty1']).toBeUndefined(); - expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); - expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); - }); - - it('should return undefined for all private properties', () => { - expect((commonInstance as any).privateProperty1).toBeUndefined(); - expect((commonInstance as any).privateProperty2).toBeUndefined(); - expect((commonInstance as any).privateGetterProperty1).toBeUndefined(); - expect((commonInstance as any).privateSetterProperty1).toBeUndefined(); - expect((commonInstance as any).privateGetterSetterProperty1).toBeUndefined(); - - expect(commonInstance['privateProperty1']).toBeUndefined(); - expect(commonInstance['privateProperty2']).toBeUndefined(); - expect(commonInstance['privateGetterProperty1']).toBeUndefined(); - expect(commonInstance['privateSetterProperty1']).toBeUndefined(); - expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); - }); - - it('should return undefined for non-exist properties', () => { - expect((commonInstance as any).nonExistProperty).toBeUndefined(); - expect(commonInstance['nonExistProperty']).toBeUndefined(); - }); - - it('should persist the first modification on properties', () => { - commonInstance.publicProperty1 = 'new-value'; - expect(commonInstance.publicProperty1).toBe('new-value'); - - commonInstance['protectedProperty1'] = 'new-value'; - expect(commonInstance['protectedProperty1']).toBe('new-value'); - - commonInstance['privateProperty1'] = 'new-value'; - expect(commonInstance['privateProperty1']).toBe('new-value'); - - commonInstance['nonExistProperty'] = 'new-value'; - expect(commonInstance['nonExistProperty']).toBe('new-value'); - }); - - it('should persist all modifications on properties', () => { - commonInstance.publicProperty1 = 'new-value-1'; - commonInstance.publicProperty1 = 'new-value-2'; - expect(commonInstance.publicProperty1).toBe('new-value-2'); - - commonInstance['protectedProperty1'] = 'new-value-1'; - commonInstance['protectedProperty1'] = 'new-value-2'; - expect(commonInstance['protectedProperty1']).toBe('new-value-2'); - - commonInstance['privateProperty1'] = 'new-value-1'; - commonInstance['privateProperty1'] = 'new-value-2'; - expect(commonInstance['privateProperty1']).toBe('new-value-2'); - - commonInstance['nonExistProperty'] = 'new-value-1'; - commonInstance['nonExistProperty'] = 'new-value-2'; - expect(commonInstance['nonExistProperty']).toBe('new-value-2'); - }); - - it('should ignore the first direct modification on properties with setters', () => { - commonInstance.publicSetterProperty1 = 'new-value'; - expect(commonInstance.publicSetterProperty1).toBeUndefined(); - commonInstance.publicGetterSetterProperty1 = 'new-value'; - expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); - - commonInstance['protectedSetterProperty1'] = 'new-value'; - expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); - commonInstance['protectedGetterSetterProperty1'] = 'new-value'; - expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); - - commonInstance['privateSetterProperty1'] = 'new-value'; - expect(commonInstance['privateSetterProperty1']).toBeUndefined(); - commonInstance['privateGetterSetterProperty1'] = 'new-value'; - expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); - }); - - it('should ignore all direct modifications on properties with setters', () => { - commonInstance.publicSetterProperty1 = 'new-value-1'; - commonInstance.publicSetterProperty1 = 'new-value-2'; - expect(commonInstance.publicSetterProperty1).toBeUndefined(); - commonInstance.publicGetterSetterProperty1 = 'new-value-1'; - commonInstance.publicGetterSetterProperty1 = 'new-value-2'; - expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); - - commonInstance['protectedSetterProperty1'] = 'new-value-1'; - commonInstance['protectedSetterProperty1'] = 'new-value-2'; - expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); - commonInstance['protectedGetterSetterProperty1'] = 'new-value-1'; - commonInstance['protectedGetterSetterProperty1'] = 'new-value-2'; - expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); - - commonInstance['privateSetterProperty1'] = 'new-value-1'; - commonInstance['privateSetterProperty1'] = 'new-value-2'; - expect(commonInstance['privateSetterProperty1']).toBeUndefined(); - commonInstance['privateGetterSetterProperty1'] = 'new-value-1'; - commonInstance['privateGetterSetterProperty1'] = 'new-value-2'; - expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); - }); - - it('should throw for modifications on properties with only getters', () => { - expect(() => (commonInstance as any).publicGetterProperty1 = 'new-value').toThrow(); - expect(() => (commonInstance as any).protectedGetterProperty1 = 'new-value').toThrow(); - expect(() => (commonInstance as any).privateGetterProperty1 = 'new-value').toThrow(); - }); - - it('should spy getters before they are used', () => { - expect(commonInstance._spy.publicGetterProperty1._get).not.toHaveBeenCalled(); - expect(commonInstance._spy.publicGetterSetterProperty1._get).not.toHaveBeenCalled(); - - expect(commonInstance._spy['protectedGetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterSetterProperty1']._get).not.toHaveBeenCalled(); - - expect(commonInstance._spy['privateGetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterSetterProperty1']._get).not.toHaveBeenCalled(); - }); - - it('should spy setters before they are used', () => { - expect(commonInstance._spy.publicSetterProperty1._set).not.toHaveBeenCalled(); - expect(commonInstance._spy.publicGetterSetterProperty1._set).not.toHaveBeenCalled(); - - expect(commonInstance._spy['protectedSetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).not.toHaveBeenCalled(); - - expect(commonInstance._spy['privateSetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).not.toHaveBeenCalled(); - }); - - it('should record calls on getters', () => { - let temp1 = commonInstance.publicGetterProperty1; - temp1 = commonInstance.publicGetterSetterProperty1; - - expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalled(); - expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalled(); - - temp1 = commonInstance.publicGetterProperty1; - temp1 = commonInstance.publicGetterSetterProperty1; - - expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalledTimes(2); - }); - - it('should record calls on setters', () => { - commonInstance.publicSetterProperty1 = 'new-value'; - commonInstance.publicGetterSetterProperty1 = 'new-value'; - expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('new-value'); - commonInstance.publicSetterProperty1 = 'new-value'; - commonInstance.publicGetterSetterProperty1 = 'new-value'; - expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledTimes(2); - - commonInstance['protectedSetterProperty1'] = 'new-value'; - commonInstance['protectedGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - commonInstance['protectedSetterProperty1'] = 'new-value'; - commonInstance['protectedGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); - - commonInstance['privateSetterProperty1'] = 'new-value'; - commonInstance['privateGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - commonInstance['privateSetterProperty1'] = 'new-value'; - commonInstance['privateGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); - }); - - it('should modify results from getters before they are used', () => { - commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); - commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); - expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); - expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); - - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); - - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); - }); - - it('should modify results from getters after they are used', () => { - let temp1 = commonInstance.publicGetterProperty1; - temp1 = commonInstance.publicGetterSetterProperty1; - commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); - commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); - expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); - expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); - - temp1 = commonInstance['protectedGetterProperty1']; - temp1 = commonInstance['protectedGetterSetterProperty1']; - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); - - temp1 = commonInstance['privateGetterProperty1']; - temp1 = commonInstance['privateGetterSetterProperty1']; - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); - }); - - it('should re-modify results from getters', () => { - commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); - commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); - expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); - expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); - commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-2'); - commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-2'); - expect(commonInstance.publicGetterProperty1).toBe('new-value-2'); - expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-2'); - - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-2'); - expect(commonInstance['protectedGetterProperty1']).toBe('new-value-2'); - expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-2'); - - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); - expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); - expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-2'); - expect(commonInstance['privateGetterProperty1']).toBe('new-value-2'); - expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-2'); - }); - - it('should return spy for all functions', () => { - expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); - expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); - expect((commonInstance as any).privateMethod).not.toHaveBeenCalled(); - expect((commonInstance as any).privateMethod).not.toHaveBeenCalled(); - }); - - it('should register calls on each spy', () => { - commonInstance.publicMethod1('value-1'); - expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-1'); - commonInstance.publicMethod1('value-2'); - expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-2'); - expect(commonInstance.publicMethod1).toHaveBeenCalledTimes(2); - - (commonInstance as any).privateMethod('value-1'); - expect((commonInstance as any).privateMethod).toHaveBeenCalledWith('value-1'); - (commonInstance as any).privateMethod('value-2'); - expect((commonInstance as any).privateMethod).toHaveBeenCalledWith('value-2'); - expect((commonInstance as any).privateMethod).toHaveBeenCalledTimes(2); - - commonInstance.publicGetterSetterProperty1 = 'value-1'; - expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('value-1'); - commonInstance.publicGetterSetterProperty1 = 'value-2'; - expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('value-2'); - expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledTimes(2); - - const whatever1 = commonInstance.publicGetterSetterProperty1; - expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalled(); - const whatever2 = commonInstance.publicGetterSetterProperty1; - expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalledTimes(2); - }); - - it('should allow spy setup before its first call', () => { - (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); - expect(commonInstance.publicMethod1('whatever')).toBe(999); - expect(commonInstance.publicMethod1('whatever')).toBe(999); - ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((commonInstance as any).privateMethod('whatever')).toBe(999); - expect((commonInstance as any).privateMethod('whatever')).toBe(999); + it('should spy setter', () => { + (mockLocalStorage as any).length = 42; + expect(mockLocalStorage._spy.length._set).toHaveBeenCalledWith(42); }); - it('should allow spy setup after its first call', () => { - commonInstance.publicMethod1('whatever'); - - (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(111); - expect(commonInstance.publicMethod1('whatever')).toBe(111); - (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); - expect(commonInstance.publicMethod1('whatever')).toBe(999); - - (commonInstance as any).privateMethod('whatever'); - - ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(111); - expect((commonInstance as any).privateMethod('whatever')).toBe(111); - ((commonInstance as any).privateMethod as jasmine.Spy).and.returnValue(999); - expect((commonInstance as any).privateMethod('whatever')).toBe(999); - }); - - it('should allow spy to be added with non-exist names', () => { - (commonInstance as any).nonExistMethod = jasmine.createSpy('newSpy'); - (commonInstance as any).nonExistMethod('value-1'); - expect((commonInstance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); - }); - - it('should allow spy to be replaced before its first call', () => { - const newSpy1 = jasmine.createSpy('newSpy') - expect(() => commonInstance.publicMethod1 = newSpy1).not.toThrowError(); - commonInstance.publicMethod1('value-1'); - - expect(newSpy1).toHaveBeenCalledWith('value-1'); - - const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (commonInstance as any).privateMethod = newSpy2).not.toThrowError(); - (commonInstance as any).privateMethod('value-1'); - - expect(newSpy2).toHaveBeenCalledWith('value-1'); - }); - - it('should allow spy to be replaced after its first call', () => { - commonInstance.publicMethod1('whatever'); - const newSpy1 = jasmine.createSpy('newSpy') - expect(() => commonInstance.publicMethod1 = newSpy1).not.toThrowError(); - commonInstance.publicMethod1('value-1'); - - expect(newSpy1).toHaveBeenCalledWith('value-1'); - - (commonInstance as any).privateMethod('whatever'); - const newSpy2 = jasmine.createSpy('newSpy') - expect(() => (commonInstance as any).privateMethod = newSpy2).not.toThrowError(); - (commonInstance as any).privateMethod('value-1'); - - expect(newSpy2).toHaveBeenCalledWith('value-1'); + it('should persist modification on properties', () => { + (mockLocalStorage as any).length = 42; + expect(mockLocalStorage.length).toBe(42); }); + }); - it('should throw when _spy facade is modified', () => { - expect(() => commonInstance._spy = 42 as any).toThrow(); - expect(() => commonInstance._spy.publicGetterSetterProperty1 = {} as any).toThrow(); + function runCommonSpecs() { + describe('meta', () => { + it('should not allow _spyFacade to be replaced', () => { + expect(() => commonInstance._spy = {} as any).toThrow(); + }); + }); + + describe('properties', () => { + it('should return undefined for all public properties', () => { + expect(commonInstance.publicProperty1).toBeUndefined(); + expect(commonInstance.publicProperty2).toBeUndefined(); + expect(commonInstance.publicGetterProperty1).toBeUndefined(); + expect(commonInstance.publicSetterProperty1).toBeUndefined(); + expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['publicProperty1']).toBeUndefined(); + expect(commonInstance['publicProperty2']).toBeUndefined(); + expect(commonInstance['publicGetterProperty1']).toBeUndefined(); + expect(commonInstance['publicSetterProperty1']).toBeUndefined(); + expect(commonInstance['publicGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for all protected properties', () => { + expect((commonInstance as any).protectedProperty1).toBeUndefined(); + expect((commonInstance as any).protectedProperty2).toBeUndefined(); + expect((commonInstance as any).protectedGetterProperty1).toBeUndefined(); + expect((commonInstance as any).protectedSetterProperty1).toBeUndefined(); + expect((commonInstance as any).protectedGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['protectedProperty1']).toBeUndefined(); + expect(commonInstance['protectedProperty2']).toBeUndefined(); + expect(commonInstance['protectedGetterProperty1']).toBeUndefined(); + expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); + expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for all private properties', () => { + expect((commonInstance as any).privateProperty1).toBeUndefined(); + expect((commonInstance as any).privateProperty2).toBeUndefined(); + expect((commonInstance as any).privateGetterProperty1).toBeUndefined(); + expect((commonInstance as any).privateSetterProperty1).toBeUndefined(); + expect((commonInstance as any).privateGetterSetterProperty1).toBeUndefined(); + + expect(commonInstance['privateProperty1']).toBeUndefined(); + expect(commonInstance['privateProperty2']).toBeUndefined(); + expect(commonInstance['privateGetterProperty1']).toBeUndefined(); + expect(commonInstance['privateSetterProperty1']).toBeUndefined(); + expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); + }); + + it('should return undefined for non-exist properties', () => { + expect((commonInstance as any).nonExistProperty).toBeUndefined(); + expect(commonInstance['nonExistProperty']).toBeUndefined(); + }); + + it('should persist first modifications on properties', () => { + commonInstance.publicProperty1 = 'new-value'; + expect(commonInstance.publicProperty1).toBe('new-value'); + (commonInstance as any).publicGetterProperty1 = 'new-value'; + expect(commonInstance.publicGetterProperty1).toBe('new-value'); + commonInstance.publicSetterProperty1 = 'new-value'; + expect(commonInstance.publicSetterProperty1).toBe('new-value'); + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value'); + + commonInstance['protectedProperty1'] = 'new-value'; + expect(commonInstance['protectedProperty1']).toBe('new-value'); + commonInstance['protectedGetterProperty1'] = 'new-value'; + expect(commonInstance['protectedGetterProperty1']).toBe('new-value'); + commonInstance['protectedSetterProperty1'] = 'new-value'; + expect(commonInstance['protectedSetterProperty1']).toBe('new-value'); + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value'); + + commonInstance['privateProperty1'] = 'new-value'; + expect(commonInstance['privateProperty1']).toBe('new-value'); + commonInstance['privateGetterProperty1'] = 'new-value'; + expect(commonInstance['privateGetterProperty1']).toBe('new-value'); + commonInstance['privateSetterProperty1'] = 'new-value'; + expect(commonInstance['privateSetterProperty1']).toBe('new-value'); + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value'); + + commonInstance['nonExistProperty'] = 'new-value'; + expect(commonInstance['nonExistProperty']).toBe('new-value'); + }); + + it('should persist all modifications on properties', () => { + commonInstance.publicProperty1 = 'new-value-1'; + commonInstance.publicProperty1 = 'new-value-2'; + expect(commonInstance.publicProperty1).toBe('new-value-2'); + (commonInstance as any).publicGetterProperty1 = 'new-value-1'; + (commonInstance as any).publicGetterProperty1 = 'new-value-2'; + expect(commonInstance.publicGetterProperty1).toBe('new-value-2'); + commonInstance.publicSetterProperty1 = 'new-value-1'; + commonInstance.publicSetterProperty1 = 'new-value-2'; + expect(commonInstance.publicSetterProperty1).toBe('new-value-2'); + commonInstance.publicGetterSetterProperty1 = 'new-value-1'; + commonInstance.publicGetterSetterProperty1 = 'new-value-2'; + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-2'); + + commonInstance['protectedProperty1'] = 'new-value-1'; + commonInstance['protectedProperty1'] = 'new-value-2'; + expect(commonInstance['protectedProperty1']).toBe('new-value-2'); + commonInstance['protectedGetterProperty1'] = 'new-value-1'; + commonInstance['protectedGetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-2'); + commonInstance['protectedSetterProperty1'] = 'new-value-1'; + commonInstance['protectedSetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedSetterProperty1']).toBe('new-value-2'); + commonInstance['protectedGetterSetterProperty1'] = 'new-value-1'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-2'); + + commonInstance['privateProperty1'] = 'new-value-1'; + commonInstance['privateProperty1'] = 'new-value-2'; + expect(commonInstance['privateProperty1']).toBe('new-value-2'); + commonInstance['privateGetterProperty1'] = 'new-value-1'; + commonInstance['privateGetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateGetterProperty1']).toBe('new-value-2'); + commonInstance['privateSetterProperty1'] = 'new-value-1'; + commonInstance['privateSetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateSetterProperty1']).toBe('new-value-2'); + commonInstance['privateGetterSetterProperty1'] = 'new-value-1'; + commonInstance['privateGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-2'); + + commonInstance['nonExistProperty'] = 'new-value-1'; + commonInstance['nonExistProperty'] = 'new-value-2'; + expect(commonInstance['nonExistProperty']).toBe('new-value-2'); + }); + + it('should spy getters before they are used', () => { + expect(commonInstance._spy.publicProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicSetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._get).not.toHaveBeenCalled(); + + expect(commonInstance._spy['protectedProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedSetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterSetterProperty1']._get).not.toHaveBeenCalled(); + + expect(commonInstance._spy['privateProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateSetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterSetterProperty1']._get).not.toHaveBeenCalled(); + + expect(commonInstance._spy['nonExistProperty']._get).not.toHaveBeenCalled(); + }); + + it('should spy setters before they are used', () => { + expect(commonInstance._spy.publicProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicSetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._set).not.toHaveBeenCalled(); + + expect(commonInstance._spy['protectedProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).not.toHaveBeenCalled(); + + expect(commonInstance._spy['privateProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).not.toHaveBeenCalled(); + + expect(commonInstance._spy['nonExistProperty']._set).not.toHaveBeenCalled(); + }); + + it('should record calls on getters', () => { + let temp1 = commonInstance.publicProperty1; + temp1 = commonInstance.publicGetterProperty1; + temp1 = commonInstance.publicSetterProperty1; + temp1 = commonInstance.publicGetterSetterProperty1; + expect(commonInstance._spy.publicProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.publicSetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalled(); + temp1 = commonInstance.publicProperty1; + temp1 = commonInstance.publicGetterProperty1; + temp1 = commonInstance.publicSetterProperty1; + temp1 = commonInstance.publicGetterSetterProperty1; + expect(commonInstance._spy.publicProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicSetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterSetterProperty1._get).toHaveBeenCalledTimes(2); + + temp1 = commonInstance['protectedProperty1']; + temp1 = commonInstance['protectedGetterProperty1']; + temp1 = commonInstance['protectedSetterProperty1']; + temp1 = commonInstance['protectedGetterSetterProperty1']; + expect(commonInstance._spy['protectedProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['protectedSetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['protectedGetterSetterProperty1']._get).toHaveBeenCalled(); + temp1 = commonInstance['protectedProperty1']; + temp1 = commonInstance['protectedGetterProperty1']; + temp1 = commonInstance['protectedSetterProperty1']; + temp1 = commonInstance['protectedGetterSetterProperty1']; + expect(commonInstance._spy['protectedProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedGetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedSetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedGetterSetterProperty1']._get).toHaveBeenCalledTimes(2); + + temp1 = commonInstance['privateProperty1']; + temp1 = commonInstance['privateGetterProperty1']; + temp1 = commonInstance['privateSetterProperty1']; + temp1 = commonInstance['privateGetterSetterProperty1']; + expect(commonInstance._spy['privateProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['privateSetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy['privateGetterSetterProperty1']._get).toHaveBeenCalled(); + temp1 = commonInstance['privateProperty1']; + temp1 = commonInstance['privateGetterProperty1']; + temp1 = commonInstance['privateSetterProperty1']; + temp1 = commonInstance['privateGetterSetterProperty1']; + expect(commonInstance._spy['privateProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateGetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateSetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateGetterSetterProperty1']._get).toHaveBeenCalledTimes(2); + + temp1 = commonInstance['nonExistProperty']; + expect(commonInstance._spy['nonExistProperty']._get).toHaveBeenCalled(); + temp1 = commonInstance['nonExistProperty']; + expect(commonInstance._spy['nonExistProperty']._get).toHaveBeenCalledTimes(2); + }); + + it('should record calls on setters', () => { + commonInstance.publicProperty1 = 'new-value'; + (commonInstance as any).publicGetterProperty1 = 'new-value'; + commonInstance.publicSetterProperty1 = 'new-value'; + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance._spy.publicProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.publicGetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledWith('new-value'); + commonInstance.publicProperty1 = 'new-value'; + (commonInstance as any).publicGetterProperty1 = 'new-value'; + commonInstance.publicSetterProperty1 = 'new-value'; + commonInstance.publicGetterSetterProperty1 = 'new-value'; + expect(commonInstance._spy.publicProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicSetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.publicGetterSetterProperty1._set).toHaveBeenCalledTimes(2); + + commonInstance['protectedProperty1'] = 'new-value'; + commonInstance['protectedGetterProperty1'] = 'new-value'; + commonInstance['protectedSetterProperty1'] = 'new-value'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['protectedProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['protectedGetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + commonInstance['protectedProperty1'] = 'new-value'; + commonInstance['protectedGetterProperty1'] = 'new-value'; + commonInstance['protectedSetterProperty1'] = 'new-value'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['protectedProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedGetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + + commonInstance['privateProperty1'] = 'new-value'; + commonInstance['privateGetterProperty1'] = 'new-value'; + commonInstance['privateSetterProperty1'] = 'new-value'; + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['privateProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['privateGetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + commonInstance['privateProperty1'] = 'new-value'; + commonInstance['privateGetterProperty1'] = 'new-value'; + commonInstance['privateSetterProperty1'] = 'new-value'; + commonInstance['privateGetterSetterProperty1'] = 'new-value'; + expect(commonInstance._spy['privateProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateGetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + + commonInstance['nonExistProperty'] = 'new-value'; + expect(commonInstance._spy['nonExistProperty']._set).toHaveBeenCalledWith('new-value'); + commonInstance['nonExistProperty'] = 'new-value'; + expect(commonInstance._spy['nonExistProperty']._set).toHaveBeenCalledTimes(2); + }); + + it('should modify results from getters before they are used', () => { + commonInstance._spy.publicProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + expect(commonInstance.publicProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicSetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + + commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['protectedProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + + commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['privateProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + + commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); + expect(commonInstance['nonExistProperty']).toBe('new-value-1'); + }); + + it('should modify results from getters after they are used', () => { + let temp = commonInstance.publicProperty1; + temp = commonInstance.publicGetterProperty1; + temp = commonInstance.publicSetterProperty1; + temp = commonInstance.publicGetterSetterProperty1; + commonInstance._spy.publicProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + expect(commonInstance.publicProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicSetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + + temp = commonInstance['protectedProperty1']; + temp = commonInstance['protectedGetterProperty1']; + temp = commonInstance['protectedSetterProperty1']; + temp = commonInstance['protectedGetterSetterProperty1']; + commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['protectedProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + + temp = commonInstance['privateProperty1']; + temp = commonInstance['privateGetterProperty1']; + temp = commonInstance['privateSetterProperty1']; + temp = commonInstance['privateGetterSetterProperty1'] + commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + expect(commonInstance['privateProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + + temp = commonInstance['nonExistProperty']; + commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); + expect(commonInstance['nonExistProperty']).toBe('new-value-1'); + }); + + it('should re-modify results from getters', () => { + commonInstance._spy.publicProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.publicSetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-2'); + expect(commonInstance.publicProperty1).toBe('new-value-2'); + expect(commonInstance.publicGetterProperty1).toBe('new-value-2'); + expect(commonInstance.publicSetterProperty1).toBe('new-value-2'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-2'); + + commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-2'); + expect(commonInstance['protectedProperty1']).toBe('new-value-2'); + expect(commonInstance['protectedGetterProperty1']).toBe('new-value-2'); + expect(commonInstance['protectedSetterProperty1']).toBe('new-value-2'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-2'); + + commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-2'); + expect(commonInstance['privateProperty1']).toBe('new-value-2'); + expect(commonInstance['privateGetterProperty1']).toBe('new-value-2'); + expect(commonInstance['privateSetterProperty1']).toBe('new-value-2'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-2'); + + commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); + commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-2'); + expect(commonInstance['nonExistProperty']).toBe('new-value-2'); + }); + + it('should not allow spiedMembers to be replaced via spyFacade', () => { + expect(() => commonInstance._spy.publicProperty1 = {}).toThrow(); + expect(() => (commonInstance._spy as any).publicGetterProperty1 = {}).toThrow(); + expect(() => commonInstance._spy.publicSetterProperty1 = {}).toThrow(); + expect(() => commonInstance._spy.publicGetterSetterProperty1 = {}).toThrow(); + + expect(() => (commonInstance._spy['protectedProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['protectedGetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['protectedSetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['protectedGetterSetterProperty1'] = {})).toThrow(); + + expect(() => (commonInstance._spy['privateProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['privateGetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['privateSetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy['privateGetterSetterProperty1'] = {})).toThrow(); + + expect(() => (commonInstance._spy['nonExistProperty'] = {})).toThrow(); + }); + + it('should not allow reading _func on properties', () => { + expect(() => commonInstance._spy.publicProperty1._func).toThrow(); + expect(() => commonInstance._spy.publicGetterProperty1._func).toThrow(); + expect(() => commonInstance._spy.publicSetterProperty1._func).toThrow(); + expect(() => commonInstance._spy.publicGetterSetterProperty1._func).toThrow(); + + expect(() => (commonInstance._spy['protectedProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['protectedGetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['protectedSetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['protectedGetterSetterProperty1']._func)).toThrow(); + + expect(() => (commonInstance._spy['privateProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['privateGetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['privateSetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy['privateGetterSetterProperty1']._func)).toThrow(); + + expect(() => (commonInstance._spy['nonExistProperty']._func)).toThrow(); + }); + + it('should not allow writing _func on properties', () => { + expect(() => commonInstance._spy.publicProperty1._func = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.publicGetterProperty1._func = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.publicSetterProperty1._func = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.publicGetterSetterProperty1._func = jasmine.createSpy('whatever')).toThrow(); + + expect(() => (commonInstance._spy['protectedProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['protectedGetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['protectedSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['protectedGetterSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + + expect(() => (commonInstance._spy['privateProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['privateGetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['privateSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy['privateGetterSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + + expect(() => (commonInstance._spy['nonExistProperty']._func = jasmine.createSpy('whatever'))).toThrow(); + }); + }); + + describe('methods', () => { + it('should return spy for all methods', () => { + expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); + expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); + expect((commonInstance as any).protectedMethod1).not.toHaveBeenCalled(); + expect((commonInstance as any).protectedMethod1).not.toHaveBeenCalled(); + expect((commonInstance as any).privateMethod1).not.toHaveBeenCalled(); + expect((commonInstance as any).privateMethod1).not.toHaveBeenCalled(); + + expect(commonInstance['publicMethod1']).not.toHaveBeenCalled(); + expect(commonInstance['publicMethod1']).not.toHaveBeenCalled(); + expect(commonInstance['protectedMethod1']).not.toHaveBeenCalled(); + expect(commonInstance['protectedMethod1']).not.toHaveBeenCalled(); + expect(commonInstance['privateMethod1']).not.toHaveBeenCalled(); + expect(commonInstance['privateMethod1']).not.toHaveBeenCalled(); + }); + + it('should return spy from spyFacade for all methods', () => { + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); + expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); + expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedMethod1']._func).not.toHaveBeenCalled(); + expect(commonInstance._spy['protectedMethod1']._func).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateMethod1']._func).not.toHaveBeenCalled(); + expect(commonInstance._spy['privateMethod1']._func).not.toHaveBeenCalled(); + }); + + it('should register calls on each spy', () => { + commonInstance.publicMethod1('value-1'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-1'); + commonInstance.publicMethod1('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledTimes(2); + + (commonInstance as any).protectedMethod1('value-1'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledWith('value-1'); + (commonInstance as any).protectedMethod1('value-2'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledWith('value-2'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledTimes(2); + + (commonInstance as any).privateMethod1('value-1'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledWith('value-1'); + (commonInstance as any).privateMethod1('value-2'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledWith('value-2'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledTimes(2); + }); + + it('should register calls on each spy accessed from the spyFacade', () => { + commonInstance._spy.publicMethod1._func('value-1'); + expect(commonInstance._spy.publicMethod1._func).toHaveBeenCalledWith('value-1'); + commonInstance._spy.publicMethod1._func('value-2'); + expect(commonInstance._spy.publicMethod1._func).toHaveBeenCalledWith('value-2'); + expect(commonInstance._spy.publicMethod1._func).toHaveBeenCalledTimes(2); + + (commonInstance._spy as any).protectedMethod1._func('value-1'); + expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledWith('value-1'); + (commonInstance._spy as any).protectedMethod1._func('value-2'); + expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledWith('value-2'); + expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledTimes(2); + + (commonInstance._spy as any).privateMethod1._func('value-1'); + expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledWith('value-1'); + (commonInstance._spy as any).privateMethod1._func('value-2'); + expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledWith('value-2'); + expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledTimes(2); + }); + + it('should register calls on each spy when calls are made after the spys are inspected', () => { + expect(commonInstance.publicMethod1).not.toHaveBeenCalled(); + commonInstance.publicMethod1('value-1'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-1'); + commonInstance.publicMethod1('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledWith('value-2'); + expect(commonInstance.publicMethod1).toHaveBeenCalledTimes(2); + + expect((commonInstance as any).protectedMethod1).not.toHaveBeenCalled(); + (commonInstance as any).protectedMethod1('value-1'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledWith('value-1'); + (commonInstance as any).protectedMethod1('value-2'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledWith('value-2'); + expect((commonInstance as any).protectedMethod1).toHaveBeenCalledTimes(2); + + expect((commonInstance as any).privateMethod1).not.toHaveBeenCalled(); + (commonInstance as any).privateMethod1('value-1'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledWith('value-1'); + (commonInstance as any).privateMethod1('value-2'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledWith('value-2'); + expect((commonInstance as any).privateMethod1).toHaveBeenCalledTimes(2); + }); + + it('should allow spies to be setup before their first calls', () => { + (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + + ((commonInstance as any).protectedMethod1 as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + + ((commonInstance as any).privateMethod1 as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + }); + + it('should allow spies to be setup through spyFacade before their first calls', () => { + commonInstance._spy.publicMethod1._func.and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + + (commonInstance._spy as any).protectedMethod1._func.and.returnValue(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + + (commonInstance._spy as any).privateMethod1._func.and.returnValue(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + }); + + it('should allow spies to be setup after their first calls', () => { + commonInstance.publicMethod1('whatever'); + (commonInstance.publicMethod1 as jasmine.Spy).and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + + (commonInstance as any).protectedMethod1('whatever'); + ((commonInstance as any).protectedMethod1 as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + + (commonInstance as any).privateMethod1('whatever'); + ((commonInstance as any).privateMethod1 as jasmine.Spy).and.returnValue(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + }); + + it('should allow spies to be setup through spyFacade after their first calls', () => { + commonInstance.publicMethod1('whatever'); + commonInstance._spy.publicMethod1._func.and.returnValue(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + expect(commonInstance.publicMethod1('whatever')).toBe(999); + + (commonInstance as any).protectedMethod1('whatever'); + (commonInstance._spy as any).protectedMethod1._func.and.returnValue(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); + + (commonInstance as any).privateMethod1('whatever'); + (commonInstance._spy as any).privateMethod1._func.and.returnValue(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + expect((commonInstance as any).privateMethod1('whatever')).toBe(999); + }); + + it('should allow spies to be added with non-exist names', () => { + (commonInstance as any).nonExistMethod = jasmine.createSpy('newSpy'); + (commonInstance as any).nonExistMethod('value-1'); + expect((commonInstance as any).nonExistMethod).toHaveBeenCalledWith('value-1'); + }); + + it('should not allow spies to be replaced before their first calls', () => { + let newSpy = jasmine.createSpy('newSpy') + expect(() => commonInstance.publicMethod1 = newSpy).toThrowError(); + + newSpy = jasmine.createSpy('newSpy') + expect(() => (commonInstance as any).protectedMethod1 = newSpy).toThrowError(); + + newSpy = jasmine.createSpy('newSpy') + expect(() => (commonInstance as any).privateMethod1 = newSpy).toThrowError(); + }); + + it('should not allow spies to be replaced after their calls', () => { + commonInstance.publicMethod1('whatever'); + expect(() => commonInstance.publicMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + + (commonInstance as any).protectedMethod1('whatever'); + expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + + (commonInstance as any).privateMethod1('whatever'); + expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + }); + + it('should not allow spies to be replaced after they are accessed from spyFacade', () => { + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect(() => commonInstance.publicMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + + expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); + expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + + expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); + }); + + it('should not allow spiedMembers to be replaced via spyFacade', () => { + expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); + expect(() => commonInstance._spy.publicMethod1 = {}).toThrowError(); + + expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); + expect(() => (commonInstance as any)._spy.privateMethod1 = {}).toThrowError(); + + expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + expect(() => (commonInstance as any)._spy.privateMethod1 = {}).toThrowError(); + }); + + it('should not allow reading _get/_set on functions', () => { + expect(() => commonInstance._spy.publicMethod1._get).toThrow(); + expect(() => commonInstance._spy.publicMethod1._set).toThrow(); + + expect(() => commonInstance._spy['protectedMethod1']._get).toThrow(); + expect(() => commonInstance._spy['protectedMethod1']._set).toThrow(); + + expect(() => commonInstance._spy['privateMethod1']._get).toThrow(); + expect(() => commonInstance._spy['privateMethod1']._set).toThrow(); + }); + + it('should not allow writing _get/_set on functions', () => { + expect(() => commonInstance._spy.publicMethod1._get = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.publicMethod1._set = jasmine.createSpy('whatever')).toThrow(); + + expect(() => commonInstance._spy['protectedMethod1']._get = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy['protectedMethod1']._set = jasmine.createSpy('whatever')).toThrow(); + + expect(() => commonInstance._spy['privateMethod1']._get = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy['privateMethod1']._set = jasmine.createSpy('whatever')).toThrow(); + }); }); } }); diff --git a/src/lib/index.ts b/src/lib/index.ts index 05e0804..ab0dd68 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -40,6 +40,10 @@ class DynamicBase { throw Error('Cannot modify _spy. It is part of the MockFactory'); } + if (typeof this.prototype[propertyName] === 'function') { + throw Error(`Cannot change ${propertyName} function, because MockFactory has already attached a permanent spy to it`) + } + this.ensureSpy(propertyName); this.stub[propertyName] = value; @@ -56,7 +60,7 @@ class DynamicBase { return this.spy[propertyName]; }, set: (target, propertyName: keyof T, value, receiver) => { - throw Error('Cannot modify spies. They are part of the MockFactory'); + throw Error(`Cannot change _spy.${propertyName}, because it is part of the MockFactory`); }, } @@ -70,37 +74,31 @@ class DynamicBase { if (!this.spy[propertyName]) { // if target is property if (typeof this.prototype[propertyName] !== 'function') { - // TODO __lookupGetter__ and __lookupSetter will be deprecated but Object.getPropertyDesriptor has not arrived. - // Consider using polyfill to be future proof - const hasGetter = !!(this.prototype as any).__lookupGetter__(propertyName); // this will lookup inherited getter/setter - const hasSetter = !!(this.prototype as any).__lookupSetter__(propertyName); - let descriptor = Object.getOwnPropertyDescriptor(this.prototype, propertyName); // this will return undefined on inherited getter/setter - descriptor = { - value: undefined, - writable: true, + // we add getters and setters to all properties to make the read and write spy-able + const descriptor = { + get: () => {}, + set: (value) => {}, enumerable: true, configurable: true, // required by spyOnProperty }; - if (hasGetter) { - descriptor.get = () => {}; - delete descriptor.value; - delete descriptor.writable; - } - - if (hasSetter) { - descriptor.set = (...arg) => {}; - delete descriptor.value; - delete descriptor.writable; - } - Object.defineProperty(this.stub, propertyName, descriptor); + // by default, let getter spy return whatever setter spy receives + const getterSpy = spyOnProperty(this.stub, propertyName, 'get'); + const setterSpy = spyOnProperty(this.stub, propertyName, 'set'); + setterSpy.and.callFake((value) => getterSpy.and.returnValue(value)); + this.spy[propertyName] = { - _func: undefined, - _get: hasGetter || (descriptor && descriptor.get) ? spyOnProperty(this.stub, propertyName, 'get') : undefined, - _set: hasSetter || (descriptor && descriptor.set) ? spyOnProperty(this.stub, propertyName, 'set') : undefined, + _get: getterSpy, + _set: setterSpy, } + + Object.defineProperty(this.spy[propertyName], '_func', { + get: () => { throw Error(`can't get ${propertyName}._func because ${propertyName} is a property. You can config getter/setter spies via ${propertyName}._get and ${propertyName}._set`); }, + set: () => { throw Error(`can't set ${propertyName}._func because ${propertyName} is a property. You can config getter/setter spies via ${propertyName}._get and ${propertyName}._set`); } + }); + // if target is function } else { const spy = jasmine.createSpy(propertyName); @@ -110,6 +108,16 @@ class DynamicBase { _get: undefined, _set: undefined, }; + + Object.defineProperty(this.spy[propertyName], '_get', { + get: () => { throw Error(`can't get ${propertyName}._get because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); }, + set: () => { throw Error(`can't set ${propertyName}._get because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); } + }); + + Object.defineProperty(this.spy[propertyName], '_set', { + get: () => { throw Error(`can't get ${propertyName}._set because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); }, + set: () => { throw Error(`can't set ${propertyName}._set because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); } + }); } } } diff --git a/tslint.json b/tslint.json index 23a2a64..64d4593 100644 --- a/tslint.json +++ b/tslint.json @@ -26,7 +26,7 @@ "label-position": true, "max-line-length": [ true, - 200 + 256 ], "member-access": false, "member-ordering": [ From 07a9205039e83f528cececdfa541ca4f49306d05 Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Thu, 29 Jun 2017 22:05:22 -0700 Subject: [PATCH 06/14] add coverage exception --- src/lib/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/index.ts b/src/lib/index.ts index ab0dd68..8f6f9ff 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -76,8 +76,8 @@ class DynamicBase { if (typeof this.prototype[propertyName] !== 'function') { // we add getters and setters to all properties to make the read and write spy-able const descriptor = { - get: () => {}, - set: (value) => {}, + get: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ () => {}, + set: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ (value) => {}, enumerable: true, configurable: true, // required by spyOnProperty }; From fab1a096e1496556ecd235310145493c3076695d Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Fri, 30 Jun 2017 00:12:31 -0700 Subject: [PATCH 07/14] update documentation; make sure spy override direct modification --- README.md | 97 ++++++++--- src/lib/index.spec.ts | 391 +++++++++++++++++++++++++----------------- src/lib/index.ts | 12 +- 3 files changed, 322 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index 560de36..4375fe7 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,14 @@ import { MockFactory} from 'jasmine-mock-factory'; it('should pass', () => { const mockInstance = MockFactory.create(SomeClass); - mockInstance.doSomething.and.returnValue('awesome!'); + /* arrange */ + mockInstance._spy.doSomething._func.and.returnValue('awesome!'); + + /* act */ mockInstance.doSomething(); // returns 'awesome!' + /* assert */ expect(mockInstance.doSomething).toHaveBeenCalled(); } ``` @@ -62,33 +66,86 @@ const realInstance: RealInterface = new RealClass(); const mockInstance = MockFactory.create(realInstance); ``` +#### From window object +```TypeScript +/* make sure you have included dom types from the TypeScript library */ +const mockWindow = MockFactory.create(window); +const mockDocument = MockFactory.create(document); +const mockLocation = MockFactory.create(location); +``` + ### Using a mock -`MockFactory.create()` will return an object with the same interface as the original object. You can invoke methods and get/set properties on this object. + * `MockFactory.create()` will return an object with the same interface as the real object. You can invoke methods and access properties on this object. + * In addition, the mock object provides a `_spy` facade, where you can access and config spies on functions and properties. +```TypeScript + const mockInstance = MockFactory.create(location); + mockInstance.reload(); // use it as if it were the real window.location + mockInstance._spy.reload._func; // returns the spy behind location.reload + mockInstance._spy.search._get; // returns the spy behind the getter for location.search +``` - * All the public and private methods will have a jasmine.Spy as the initial value. The Spy cannot be overwritten. - * All the public and private properties will have `undefined` as the initial value. The value can be overwritten with anything. - -### Examples +#### Invoking functions + * All methods will have a jasmine.Spy as the initial value. The Spy cannot be overwritten and returns `undefined` by default. + * To access protected and private functions, cast the mockInstance `as any` or using bracket notation. ```TypeScript -class RealClass { - public doSomething(...arg: any[]) { ... } - public someProperty = 'whatever'; -} + mockInstance.publicMethod(42); // the spy behind it is invoked with 42 -const mockInstance = MockFactory.create(RealClass); + (mockInstance as any).privateMethod(42); + mockInstance['privateMethod'](42); +``` + +#### Spying/stubbing functions + * You can change return values of functions or assert their calls by accessing them directly or throught the `_spy` facade. + * To access a function spy, call `mockInstance._spy.functionName._func`. + ```TypeScript + (mockInstance.publicMethod as jasmine.Spy).and.returnValue(42); // it works, but requires casting + mockInstance._spy.publicMethod._func.and.returnValue(42); // recommended! + + ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(42); // it works, but requires two castings + mockInstance._spy.privateMethod._func.and.returnValue(42); // recommended! +``` -// get, set property -expect(mockInstance.someProperty).toBeUndefined(); -mockInstance.someProperty = 'hello'; -expect(mockInstance.someProperty).toBe('hello'); +#### Accessing properties + * All properties have `undefined` as the initial value. The value can be overwritten with anything. + * You can read and write access to any property, even if they were readonly in the real object. + * To read or write value on a protected or private properties, cast the mockInstance `as any` or using bracket notation. + * To write value on a readonly property, cast the mockInstance `as any`. Note that bracket notation won't work. + * By default, modification to the properties will be preserved, even if a getter or setter was used in the real object. +```TypeScript + mockInstance.publicProperty = 42; + let temp = mockInstance.publicProperty; // temp = 42; -// use function spy -expect(mockInstance.doSomething).not.toHaveBeenCalled(); + mockInstance.readonlyProperty = 42; // typescript compiler error + (mockInstance as any).readonlyProperty = 42; // no problem + mockInstance['readonlyProperty'] = 42; // typescript compiler error -(mockInstance.doSomething as jasmine.Spy).and.returnValue('awesome!'); + (mockInstance as any).privateProperty = 'foo'; + mockInstance['privateProperty'] = 'foo'; // equivalent to above +``` -expect(mockInstance.doSomething(42)).toBe('awesome!'); -expect(mockInstance.doSomething).toHaveBeenCalledWith(42); +#### Spying/stubbing getters and setters + * All properties have spies on the getter and setter, even if they weren't in the real object. + * To access a getter spy, call `mockInstance._spy.property._get`. + * To access a setter spy, call `mockInstance._spy.property._set`. + * NOTE: modification to the properties will not be preserved after getter or setter spies are customized + * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your asserts to avoid stepping on your own foot. +```TypeScript + let temp = mockInstance.publicProperty; + expect(mockInstance._spy.publicProperty._get).toHaveBeenCalled(); + + mockInstance.publicProperty = 42; + expect(mockInstance._spy.publicProperty._set).toHaveBeenCalledWith(42); + expect(mockInstance.publicProperty).toBe(42); // pass + mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */}); + mockInstance.publicProperty = 100; + expect(mockInstance.publicProperty).toBe(100); // fail. setter has been customized + + mockInstance['privateProperty'] = 100; + expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(100); + expect(mockInstance['privateProperty']).toBe(100); // pass + mockInstance._spy.privateProperty._get.and.returnValue(42); + mockInstance['privateProperty'] = 100; + expect(mockInstance['privateProperty']).toBe(100); // fail. getter has been customzied ``` ## Develope diff --git a/src/lib/index.spec.ts b/src/lib/index.spec.ts index 1018db9..abdbaa5 100644 --- a/src/lib/index.spec.ts +++ b/src/lib/index.spec.ts @@ -391,17 +391,17 @@ describe('Using mocks', () => { expect(commonInstance._spy.publicSetterProperty1._get).not.toHaveBeenCalled(); expect(commonInstance._spy.publicGetterSetterProperty1._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedSetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterSetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedSetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterSetterProperty1._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateSetterProperty1']._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterSetterProperty1']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateSetterProperty1._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterSetterProperty1._get).not.toHaveBeenCalled(); - expect(commonInstance._spy['nonExistProperty']._get).not.toHaveBeenCalled(); + expect(commonInstance._spy.nonExistProperty._get).not.toHaveBeenCalled(); }); it('should spy setters before they are used', () => { @@ -410,17 +410,17 @@ describe('Using mocks', () => { expect(commonInstance._spy.publicSetterProperty1._set).not.toHaveBeenCalled(); expect(commonInstance._spy.publicGetterSetterProperty1._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedSetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedSetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterSetterProperty1._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateSetterProperty1']._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateSetterProperty1._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterSetterProperty1._set).not.toHaveBeenCalled(); - expect(commonInstance._spy['nonExistProperty']._set).not.toHaveBeenCalled(); + expect(commonInstance._spy.nonExistProperty._set).not.toHaveBeenCalled(); }); it('should record calls on getters', () => { @@ -445,40 +445,40 @@ describe('Using mocks', () => { temp1 = commonInstance['protectedGetterProperty1']; temp1 = commonInstance['protectedSetterProperty1']; temp1 = commonInstance['protectedGetterSetterProperty1']; - expect(commonInstance._spy['protectedProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['protectedSetterProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['protectedGetterSetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy.protectedProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.protectedSetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.protectedGetterSetterProperty1._get).toHaveBeenCalled(); temp1 = commonInstance['protectedProperty1']; temp1 = commonInstance['protectedGetterProperty1']; temp1 = commonInstance['protectedSetterProperty1']; temp1 = commonInstance['protectedGetterSetterProperty1']; - expect(commonInstance._spy['protectedProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedGetterProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedSetterProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedGetterSetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedGetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedSetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedGetterSetterProperty1._get).toHaveBeenCalledTimes(2); temp1 = commonInstance['privateProperty1']; temp1 = commonInstance['privateGetterProperty1']; temp1 = commonInstance['privateSetterProperty1']; temp1 = commonInstance['privateGetterSetterProperty1']; - expect(commonInstance._spy['privateProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['privateSetterProperty1']._get).toHaveBeenCalled(); - expect(commonInstance._spy['privateGetterSetterProperty1']._get).toHaveBeenCalled(); + expect(commonInstance._spy.privateProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.privateSetterProperty1._get).toHaveBeenCalled(); + expect(commonInstance._spy.privateGetterSetterProperty1._get).toHaveBeenCalled(); temp1 = commonInstance['privateProperty1']; temp1 = commonInstance['privateGetterProperty1']; temp1 = commonInstance['privateSetterProperty1']; temp1 = commonInstance['privateGetterSetterProperty1']; - expect(commonInstance._spy['privateProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateGetterProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateSetterProperty1']._get).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateGetterSetterProperty1']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateGetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateSetterProperty1._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateGetterSetterProperty1._get).toHaveBeenCalledTimes(2); temp1 = commonInstance['nonExistProperty']; - expect(commonInstance._spy['nonExistProperty']._get).toHaveBeenCalled(); + expect(commonInstance._spy.nonExistProperty._get).toHaveBeenCalled(); temp1 = commonInstance['nonExistProperty']; - expect(commonInstance._spy['nonExistProperty']._get).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.nonExistProperty._get).toHaveBeenCalledTimes(2); }); it('should record calls on setters', () => { @@ -503,40 +503,40 @@ describe('Using mocks', () => { commonInstance['protectedGetterProperty1'] = 'new-value'; commonInstance['protectedSetterProperty1'] = 'new-value'; commonInstance['protectedGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['protectedProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['protectedGetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.protectedProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.protectedGetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.protectedSetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.protectedGetterSetterProperty1._set).toHaveBeenCalledWith('new-value'); commonInstance['protectedProperty1'] = 'new-value'; commonInstance['protectedGetterProperty1'] = 'new-value'; commonInstance['protectedSetterProperty1'] = 'new-value'; commonInstance['protectedGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['protectedProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedGetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedSetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['protectedGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedGetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedSetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.protectedGetterSetterProperty1._set).toHaveBeenCalledTimes(2); commonInstance['privateProperty1'] = 'new-value'; commonInstance['privateGetterProperty1'] = 'new-value'; commonInstance['privateSetterProperty1'] = 'new-value'; commonInstance['privateGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['privateProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['privateGetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledWith('new-value'); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.privateProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.privateGetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.privateSetterProperty1._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.privateGetterSetterProperty1._set).toHaveBeenCalledWith('new-value'); commonInstance['privateProperty1'] = 'new-value'; commonInstance['privateGetterProperty1'] = 'new-value'; commonInstance['privateSetterProperty1'] = 'new-value'; commonInstance['privateGetterSetterProperty1'] = 'new-value'; - expect(commonInstance._spy['privateProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateGetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateSetterProperty1']._set).toHaveBeenCalledTimes(2); - expect(commonInstance._spy['privateGetterSetterProperty1']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateGetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateSetterProperty1._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.privateGetterSetterProperty1._set).toHaveBeenCalledTimes(2); commonInstance['nonExistProperty'] = 'new-value'; - expect(commonInstance._spy['nonExistProperty']._set).toHaveBeenCalledWith('new-value'); + expect(commonInstance._spy.nonExistProperty._set).toHaveBeenCalledWith('new-value'); commonInstance['nonExistProperty'] = 'new-value'; - expect(commonInstance._spy['nonExistProperty']._set).toHaveBeenCalledTimes(2); + expect(commonInstance._spy.nonExistProperty._set).toHaveBeenCalledTimes(2); }); it('should modify results from getters before they are used', () => { @@ -549,25 +549,25 @@ describe('Using mocks', () => { expect(commonInstance.publicSetterProperty1).toBe('new-value-1'); expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); - commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterSetterProperty1._get.and.returnValue('new-value-1'); expect(commonInstance['protectedProperty1']).toBe('new-value-1'); expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); expect(commonInstance['protectedSetterProperty1']).toBe('new-value-1'); expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); - commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy.privateProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterSetterProperty1._get.and.returnValue('new-value-1'); expect(commonInstance['privateProperty1']).toBe('new-value-1'); expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); expect(commonInstance['privateSetterProperty1']).toBe('new-value-1'); expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); - commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); + commonInstance._spy.nonExistProperty._get.and.returnValue('new-value-1'); expect(commonInstance['nonExistProperty']).toBe('new-value-1'); }); @@ -589,10 +589,10 @@ describe('Using mocks', () => { temp = commonInstance['protectedGetterProperty1']; temp = commonInstance['protectedSetterProperty1']; temp = commonInstance['protectedGetterSetterProperty1']; - commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterSetterProperty1._get.and.returnValue('new-value-1'); expect(commonInstance['protectedProperty1']).toBe('new-value-1'); expect(commonInstance['protectedGetterProperty1']).toBe('new-value-1'); expect(commonInstance['protectedSetterProperty1']).toBe('new-value-1'); @@ -602,17 +602,17 @@ describe('Using mocks', () => { temp = commonInstance['privateGetterProperty1']; temp = commonInstance['privateSetterProperty1']; temp = commonInstance['privateGetterSetterProperty1'] - commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); + commonInstance._spy.privateProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterSetterProperty1._get.and.returnValue('new-value-1'); expect(commonInstance['privateProperty1']).toBe('new-value-1'); expect(commonInstance['privateGetterProperty1']).toBe('new-value-1'); expect(commonInstance['privateSetterProperty1']).toBe('new-value-1'); expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); temp = commonInstance['nonExistProperty']; - commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); + commonInstance._spy.nonExistProperty._get.and.returnValue('new-value-1'); expect(commonInstance['nonExistProperty']).toBe('new-value-1'); }); @@ -630,54 +630,137 @@ describe('Using mocks', () => { expect(commonInstance.publicSetterProperty1).toBe('new-value-2'); expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-2'); - commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['protectedProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['protectedGetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['protectedSetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['protectedGetterSetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy.protectedProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.protectedGetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.protectedSetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.protectedGetterSetterProperty1._get.and.returnValue('new-value-2'); expect(commonInstance['protectedProperty1']).toBe('new-value-2'); expect(commonInstance['protectedGetterProperty1']).toBe('new-value-2'); expect(commonInstance['protectedSetterProperty1']).toBe('new-value-2'); expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-2'); - commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-1'); - commonInstance._spy['privateProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['privateGetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['privateSetterProperty1']._get.and.returnValue('new-value-2'); - commonInstance._spy['privateGetterSetterProperty1']._get.and.returnValue('new-value-2'); + commonInstance._spy.privateProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.privateGetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.privateSetterProperty1._get.and.returnValue('new-value-2'); + commonInstance._spy.privateGetterSetterProperty1._get.and.returnValue('new-value-2'); expect(commonInstance['privateProperty1']).toBe('new-value-2'); expect(commonInstance['privateGetterProperty1']).toBe('new-value-2'); expect(commonInstance['privateSetterProperty1']).toBe('new-value-2'); expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-2'); - commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-1'); - commonInstance._spy['nonExistProperty']._get.and.returnValue('new-value-2'); + commonInstance._spy.nonExistProperty._get.and.returnValue('new-value-1'); + commonInstance._spy.nonExistProperty._get.and.returnValue('new-value-2'); expect(commonInstance['nonExistProperty']).toBe('new-value-2'); }); + it('should respect getters despite direct modification on properties', () => { + commonInstance._spy.publicProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.publicGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance.publicProperty1 = 'new-value-2'; + (commonInstance as any).publicGetterProperty1 = 'new-value-2'; + commonInstance.publicSetterProperty1 = 'new-value-2'; + commonInstance.publicGetterSetterProperty1 = 'new-value-2'; + expect(commonInstance.publicProperty1).toBe('new-value-1'); + expect((commonInstance as any).publicGetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicSetterProperty1).toBe('new-value-1'); + expect(commonInstance.publicGetterSetterProperty1).toBe('new-value-1'); + + commonInstance._spy.protectedProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.protectedGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance['protectedProperty1'] = 'new-value-2'; + (commonInstance as any).protectedGetterProperty1 = 'new-value-2'; + commonInstance['protectedSetterProperty1'] = 'new-value-2'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['protectedProperty1']).toBe('new-value-1'); + expect((commonInstance as any).protectedGetterProperty1).toBe('new-value-1'); + expect(commonInstance['protectedSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['protectedGetterSetterProperty1']).toBe('new-value-1'); + + commonInstance._spy.privateProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance._spy.privateGetterSetterProperty1._get.and.returnValue('new-value-1'); + commonInstance['privateProperty1'] = 'new-value-2'; + (commonInstance as any).privateGetterProperty1 = 'new-value-2'; + commonInstance['privateSetterProperty1'] = 'new-value-2'; + commonInstance['privateGetterSetterProperty1'] = 'new-value-2'; + expect(commonInstance['privateProperty1']).toBe('new-value-1'); + expect((commonInstance as any).privateGetterProperty1).toBe('new-value-1'); + expect(commonInstance['privateSetterProperty1']).toBe('new-value-1'); + expect(commonInstance['privateGetterSetterProperty1']).toBe('new-value-1'); + }); + + it('should respect setters despite direct modification on properties', () => { + commonInstance._spy.publicProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.publicGetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.publicSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.publicGetterSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance.publicProperty1 = 'new-value-1'; + (commonInstance as any).publicGetterProperty1 = 'new-value-1'; + commonInstance.publicSetterProperty1 = 'new-value-1'; + commonInstance.publicGetterSetterProperty1 = 'new-value-1'; + expect(commonInstance.publicProperty1).toBeUndefined(); + expect((commonInstance as any).publicGetterProperty1).toBeUndefined(); + expect(commonInstance.publicSetterProperty1).toBeUndefined(); + expect(commonInstance.publicGetterSetterProperty1).toBeUndefined(); + + commonInstance._spy.protectedProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.protectedGetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.protectedSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.protectedGetterSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance['protectedProperty1'] = 'new-value-1'; + (commonInstance as any).protectedGetterProperty1 = 'new-value-1'; + commonInstance['protectedSetterProperty1'] = 'new-value-1'; + commonInstance['protectedGetterSetterProperty1'] = 'new-value-1'; + expect(commonInstance['protectedProperty1']).toBeUndefined(); + expect((commonInstance as any).protectedGetterProperty1).toBeUndefined(); + expect(commonInstance['protectedSetterProperty1']).toBeUndefined(); + expect(commonInstance['protectedGetterSetterProperty1']).toBeUndefined(); + + commonInstance._spy.privateProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.privateGetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.privateSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance._spy.privateGetterSetterProperty1._set.and.callFake(() => { /* noop */}); + commonInstance['privateProperty1'] = 'new-value-1'; + (commonInstance as any).privateGetterProperty1 = 'new-value-1'; + commonInstance['privateSetterProperty1'] = 'new-value-1'; + commonInstance['privateGetterSetterProperty1'] = 'new-value-1'; + expect(commonInstance['privateProperty1']).toBeUndefined(); + expect((commonInstance as any).privateGetterProperty1).toBeUndefined(); + expect(commonInstance['privateSetterProperty1']).toBeUndefined(); + expect(commonInstance['privateGetterSetterProperty1']).toBeUndefined(); + }); + + it('should not allow spiedMembers to be replaced via spyFacade', () => { expect(() => commonInstance._spy.publicProperty1 = {}).toThrow(); expect(() => (commonInstance._spy as any).publicGetterProperty1 = {}).toThrow(); expect(() => commonInstance._spy.publicSetterProperty1 = {}).toThrow(); expect(() => commonInstance._spy.publicGetterSetterProperty1 = {}).toThrow(); - expect(() => (commonInstance._spy['protectedProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['protectedGetterProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['protectedSetterProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['protectedGetterSetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy.protectedProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.protectedGetterProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.protectedSetterProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.protectedGetterSetterProperty1 = {})).toThrow(); - expect(() => (commonInstance._spy['privateProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['privateGetterProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['privateSetterProperty1'] = {})).toThrow(); - expect(() => (commonInstance._spy['privateGetterSetterProperty1'] = {})).toThrow(); + expect(() => (commonInstance._spy.privateProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.privateGetterProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.privateSetterProperty1 = {})).toThrow(); + expect(() => (commonInstance._spy.privateGetterSetterProperty1 = {})).toThrow(); - expect(() => (commonInstance._spy['nonExistProperty'] = {})).toThrow(); + expect(() => (commonInstance._spy.nonExistProperty = {})).toThrow(); }); it('should not allow reading _func on properties', () => { @@ -686,17 +769,17 @@ describe('Using mocks', () => { expect(() => commonInstance._spy.publicSetterProperty1._func).toThrow(); expect(() => commonInstance._spy.publicGetterSetterProperty1._func).toThrow(); - expect(() => (commonInstance._spy['protectedProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['protectedGetterProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['protectedSetterProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['protectedGetterSetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy.protectedProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.protectedGetterProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.protectedSetterProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.protectedGetterSetterProperty1._func)).toThrow(); - expect(() => (commonInstance._spy['privateProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['privateGetterProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['privateSetterProperty1']._func)).toThrow(); - expect(() => (commonInstance._spy['privateGetterSetterProperty1']._func)).toThrow(); + expect(() => (commonInstance._spy.privateProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.privateGetterProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.privateSetterProperty1._func)).toThrow(); + expect(() => (commonInstance._spy.privateGetterSetterProperty1._func)).toThrow(); - expect(() => (commonInstance._spy['nonExistProperty']._func)).toThrow(); + expect(() => (commonInstance._spy.nonExistProperty._func)).toThrow(); }); it('should not allow writing _func on properties', () => { @@ -705,17 +788,17 @@ describe('Using mocks', () => { expect(() => commonInstance._spy.publicSetterProperty1._func = jasmine.createSpy('whatever')).toThrow(); expect(() => commonInstance._spy.publicGetterSetterProperty1._func = jasmine.createSpy('whatever')).toThrow(); - expect(() => (commonInstance._spy['protectedProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['protectedGetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['protectedSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['protectedGetterSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.protectedProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.protectedGetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.protectedSetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.protectedGetterSetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['privateProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['privateGetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['privateSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['privateGetterSetterProperty1']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.privateProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.privateGetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.privateSetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.privateGetterSetterProperty1._func = jasmine.createSpy('whatever'))).toThrow(); - expect(() => (commonInstance._spy['nonExistProperty']._func = jasmine.createSpy('whatever'))).toThrow(); + expect(() => (commonInstance._spy.nonExistProperty._func = jasmine.createSpy('whatever'))).toThrow(); }); }); @@ -739,17 +822,17 @@ describe('Using mocks', () => { it('should return spy from spyFacade for all methods', () => { expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); - expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); - expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); - expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); - expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedMethod1']._func).not.toHaveBeenCalled(); - expect(commonInstance._spy['protectedMethod1']._func).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateMethod1']._func).not.toHaveBeenCalled(); - expect(commonInstance._spy['privateMethod1']._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); }); it('should register calls on each spy', () => { @@ -779,17 +862,17 @@ describe('Using mocks', () => { expect(commonInstance._spy.publicMethod1._func).toHaveBeenCalledWith('value-2'); expect(commonInstance._spy.publicMethod1._func).toHaveBeenCalledTimes(2); - (commonInstance._spy as any).protectedMethod1._func('value-1'); - expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledWith('value-1'); - (commonInstance._spy as any).protectedMethod1._func('value-2'); - expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledWith('value-2'); - expect((commonInstance._spy as any).protectedMethod1._func).toHaveBeenCalledTimes(2); - - (commonInstance._spy as any).privateMethod1._func('value-1'); - expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledWith('value-1'); - (commonInstance._spy as any).privateMethod1._func('value-2'); - expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledWith('value-2'); - expect((commonInstance._spy as any).privateMethod1._func).toHaveBeenCalledTimes(2); + commonInstance._spy.protectedMethod1._func('value-1'); + expect(commonInstance._spy.protectedMethod1._func).toHaveBeenCalledWith('value-1'); + commonInstance._spy.protectedMethod1._func('value-2'); + expect(commonInstance._spy.protectedMethod1._func).toHaveBeenCalledWith('value-2'); + expect(commonInstance._spy.protectedMethod1._func).toHaveBeenCalledTimes(2); + + commonInstance._spy.privateMethod1._func('value-1'); + expect(commonInstance._spy.privateMethod1._func).toHaveBeenCalledWith('value-1'); + commonInstance._spy.privateMethod1._func('value-2'); + expect(commonInstance._spy.privateMethod1._func).toHaveBeenCalledWith('value-2'); + expect(commonInstance._spy.privateMethod1._func).toHaveBeenCalledTimes(2); }); it('should register calls on each spy when calls are made after the spys are inspected', () => { @@ -834,11 +917,11 @@ describe('Using mocks', () => { expect(commonInstance.publicMethod1('whatever')).toBe(999); expect(commonInstance.publicMethod1('whatever')).toBe(999); - (commonInstance._spy as any).protectedMethod1._func.and.returnValue(999); + commonInstance._spy.protectedMethod1._func.and.returnValue(999); expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); - (commonInstance._spy as any).privateMethod1._func.and.returnValue(999); + commonInstance._spy.privateMethod1._func.and.returnValue(999); expect((commonInstance as any).privateMethod1('whatever')).toBe(999); expect((commonInstance as any).privateMethod1('whatever')).toBe(999); }); @@ -867,12 +950,12 @@ describe('Using mocks', () => { expect(commonInstance.publicMethod1('whatever')).toBe(999); (commonInstance as any).protectedMethod1('whatever'); - (commonInstance._spy as any).protectedMethod1._func.and.returnValue(999); + commonInstance._spy.protectedMethod1._func.and.returnValue(999); expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); expect((commonInstance as any).protectedMethod1('whatever')).toBe(999); (commonInstance as any).privateMethod1('whatever'); - (commonInstance._spy as any).privateMethod1._func.and.returnValue(999); + commonInstance._spy.privateMethod1._func.and.returnValue(999); expect((commonInstance as any).privateMethod1('whatever')).toBe(999); expect((commonInstance as any).privateMethod1('whatever')).toBe(999); }); @@ -909,10 +992,10 @@ describe('Using mocks', () => { expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); expect(() => commonInstance.publicMethod1 = jasmine.createSpy('newSpy')).toThrowError(); - expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); - expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); expect(() => (commonInstance as any).privateMethod1 = jasmine.createSpy('newSpy')).toThrowError(); }); @@ -920,33 +1003,33 @@ describe('Using mocks', () => { expect(commonInstance._spy.publicMethod1._func).not.toHaveBeenCalled(); expect(() => commonInstance._spy.publicMethod1 = {}).toThrowError(); - expect((commonInstance._spy as any).protectedMethod1._func).not.toHaveBeenCalled(); - expect(() => (commonInstance as any)._spy.privateMethod1 = {}).toThrowError(); + expect(commonInstance._spy.protectedMethod1._func).not.toHaveBeenCalled(); + expect(() => commonInstance._spy.privateMethod1 = {}).toThrowError(); - expect((commonInstance._spy as any).privateMethod1._func).not.toHaveBeenCalled(); - expect(() => (commonInstance as any)._spy.privateMethod1 = {}).toThrowError(); + expect(commonInstance._spy.privateMethod1._func).not.toHaveBeenCalled(); + expect(() => commonInstance._spy.privateMethod1 = {}).toThrowError(); }); it('should not allow reading _get/_set on functions', () => { expect(() => commonInstance._spy.publicMethod1._get).toThrow(); expect(() => commonInstance._spy.publicMethod1._set).toThrow(); - expect(() => commonInstance._spy['protectedMethod1']._get).toThrow(); - expect(() => commonInstance._spy['protectedMethod1']._set).toThrow(); + expect(() => commonInstance._spy.protectedMethod1._get).toThrow(); + expect(() => commonInstance._spy.protectedMethod1._set).toThrow(); - expect(() => commonInstance._spy['privateMethod1']._get).toThrow(); - expect(() => commonInstance._spy['privateMethod1']._set).toThrow(); + expect(() => commonInstance._spy.privateMethod1._get).toThrow(); + expect(() => commonInstance._spy.privateMethod1._set).toThrow(); }); it('should not allow writing _get/_set on functions', () => { expect(() => commonInstance._spy.publicMethod1._get = jasmine.createSpy('whatever')).toThrow(); expect(() => commonInstance._spy.publicMethod1._set = jasmine.createSpy('whatever')).toThrow(); - expect(() => commonInstance._spy['protectedMethod1']._get = jasmine.createSpy('whatever')).toThrow(); - expect(() => commonInstance._spy['protectedMethod1']._set = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.protectedMethod1._get = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.protectedMethod1._set = jasmine.createSpy('whatever')).toThrow(); - expect(() => commonInstance._spy['privateMethod1']._get = jasmine.createSpy('whatever')).toThrow(); - expect(() => commonInstance._spy['privateMethod1']._set = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.privateMethod1._get = jasmine.createSpy('whatever')).toThrow(); + expect(() => commonInstance._spy.privateMethod1._set = jasmine.createSpy('whatever')).toThrow(); }); }); } diff --git a/src/lib/index.ts b/src/lib/index.ts index 8f6f9ff..0f97e07 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,13 +1,17 @@ export declare type Mock = T & SpyFacade; export interface SpyFacade { - _spy: Spied; + _spy: Spied & SpiedAny; } export declare type Spied = { [K in keyof T]: SpiedMember; } +export interface SpiedAny { + [id: string]: SpiedMember +} + export interface SpiedMember { _func?: jasmine.Spy; _get?: jasmine.Spy; @@ -85,11 +89,11 @@ class DynamicBase { Object.defineProperty(this.stub, propertyName, descriptor); // by default, let getter spy return whatever setter spy receives - const getterSpy = spyOnProperty(this.stub, propertyName, 'get'); - const setterSpy = spyOnProperty(this.stub, propertyName, 'set'); - setterSpy.and.callFake((value) => getterSpy.and.returnValue(value)); + const getterSpy = spyOnProperty(this.stub, propertyName, 'get').and.callFake(() => this.spy[propertyName]._value); + const setterSpy = spyOnProperty(this.stub, propertyName, 'set').and.callFake(value => this.spy[propertyName]._value = value); this.spy[propertyName] = { + _value: undefined, // this is not on the public API, because _value will become meaningless once user customizes the spies. _get: getterSpy, _set: setterSpy, } From 05d752a3feea7ab0e60e6ca0d9653b5df013771c Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Fri, 30 Jun 2017 00:18:22 -0700 Subject: [PATCH 08/14] add metadata for 2.0.0 --- CHANGELOG.md | 3 +++ README.md | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3681781 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 2.0.0 + * Suuport complete typing of spies through the `_spy` facade. + * Allow spying and stubbing value accessors on properties with or without getter and setter. \ No newline at end of file diff --git a/README.md b/README.md index 4375fe7..11b9f5f 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ const mockLocation = MockFactory.create(location); * To access a getter spy, call `mockInstance._spy.property._get`. * To access a setter spy, call `mockInstance._spy.property._set`. * NOTE: modification to the properties will not be preserved after getter or setter spies are customized - * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your asserts to avoid stepping on your own foot. + * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions to avoid stepping on your own foot. ```TypeScript let temp = mockInstance.publicProperty; expect(mockInstance._spy.publicProperty._get).toHaveBeenCalled(); diff --git a/package.json b/package.json index 692293b..0ce42af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jasmine-mock-factory", - "version": "1.0.4", + "version": "2.0.0", "description": "A Jasmine helper for creating mocked classes", "license": "MIT", "author": "Chuanqi Sun", From 7d71fb96c5b049a826a9c8f1cdce4f6813a78a39 Mon Sep 17 00:00:00 2001 From: Chuanqi Date: Fri, 30 Jun 2017 00:20:04 -0700 Subject: [PATCH 09/14] wording on changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3681781..497c7fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ # 2.0.0 - * Suuport complete typing of spies through the `_spy` facade. - * Allow spying and stubbing value accessors on properties with or without getter and setter. \ No newline at end of file + * Support typing of spies through the `_spy` facade. + * Allow spying and stubbing value accessors on any property \ No newline at end of file From a07d0ce5399b15591c5d2e571bc69581b819333b Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Fri, 30 Jun 2017 17:44:19 -0700 Subject: [PATCH 10/14] update documentation --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 11b9f5f..97d720a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ it('should pass', () => { This util is built with and for [Jasmine](https://jasmine.github.io/) test framework. Basic understanding of Jasmine is assumed. -This util requires [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and only contains un-compiled `*.ts` files which must be compiled with a [TypeScript](https://www.typescriptlang.org/) compiler. +This util requires [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and only contains `*.ts` files that must be compiled with a [TypeScript](https://www.typescriptlang.org/) compiler. @@ -66,7 +66,7 @@ const realInstance: RealInterface = new RealClass(); const mockInstance = MockFactory.create(realInstance); ``` -#### From window object +#### From window objects ```TypeScript /* make sure you have included dom types from the TypeScript library */ const mockWindow = MockFactory.create(window); @@ -85,8 +85,8 @@ const mockLocation = MockFactory.create(location); ``` #### Invoking functions - * All methods will have a jasmine.Spy as the initial value. The Spy cannot be overwritten and returns `undefined` by default. - * To access protected and private functions, cast the mockInstance `as any` or using bracket notation. + * All methods will have a `jasmine.Spy` as the initial value. The spy cannot be overwritten and returns `undefined` by default. + * To access protected and private functions, cast the mockInstance `as any` or use bracket notation. ```TypeScript mockInstance.publicMethod(42); // the spy behind it is invoked with 42 @@ -95,22 +95,22 @@ const mockLocation = MockFactory.create(location); ``` #### Spying/stubbing functions - * You can change return values of functions or assert their calls by accessing them directly or throught the `_spy` facade. - * To access a function spy, call `mockInstance._spy.functionName._func`. + * You can change return values of functions or assert their calls by accessing them directly or through the `_spy` facade. + * Access a function spy on `mockInstance._spy.functionName._func`. ```TypeScript - (mockInstance.publicMethod as jasmine.Spy).and.returnValue(42); // it works, but requires casting - mockInstance._spy.publicMethod._func.and.returnValue(42); // recommended! + mockInstance._spy.publicMethod._func.and.returnValue(42); + (mockInstance.publicMethod as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting - ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(42); // it works, but requires two castings - mockInstance._spy.privateMethod._func.and.returnValue(42); // recommended! + mockInstance._spy.privateMethod._func.and.returnValue(42); + ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting twice ``` #### Accessing properties * All properties have `undefined` as the initial value. The value can be overwritten with anything. - * You can read and write access to any property, even if they were readonly in the real object. - * To read or write value on a protected or private properties, cast the mockInstance `as any` or using bracket notation. + * You have read and write access to any property, even if they were readonly in the real object. + * To read or write value on a protected or private properties, cast the mockInstance `as any` or use bracket notation. * To write value on a readonly property, cast the mockInstance `as any`. Note that bracket notation won't work. - * By default, modification to the properties will be preserved, even if a getter or setter was used in the real object. + * By default, modification to the properties will be preserved, even if a getter or setter exists in the real object. ```TypeScript mockInstance.publicProperty = 42; let temp = mockInstance.publicProperty; // temp = 42; @@ -125,10 +125,10 @@ const mockLocation = MockFactory.create(location); #### Spying/stubbing getters and setters * All properties have spies on the getter and setter, even if they weren't in the real object. - * To access a getter spy, call `mockInstance._spy.property._get`. - * To access a setter spy, call `mockInstance._spy.property._set`. + * Access a getter spy on `mockInstance._spy.property._get`. + * Access a setter spy on `mockInstance._spy.property._set`. * NOTE: modification to the properties will not be preserved after getter or setter spies are customized - * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions to avoid stepping on your own foot. + * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions to avoid shooting yourself in the foot. ```TypeScript let temp = mockInstance.publicProperty; expect(mockInstance._spy.publicProperty._get).toHaveBeenCalled(); @@ -136,14 +136,14 @@ const mockLocation = MockFactory.create(location); mockInstance.publicProperty = 42; expect(mockInstance._spy.publicProperty._set).toHaveBeenCalledWith(42); expect(mockInstance.publicProperty).toBe(42); // pass - mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */}); + mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */}); // customized setter mockInstance.publicProperty = 100; expect(mockInstance.publicProperty).toBe(100); // fail. setter has been customized mockInstance['privateProperty'] = 100; expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(100); expect(mockInstance['privateProperty']).toBe(100); // pass - mockInstance._spy.privateProperty._get.and.returnValue(42); + mockInstance._spy.privateProperty._get.and.returnValue(42); // customized getter mockInstance['privateProperty'] = 100; expect(mockInstance['privateProperty']).toBe(100); // fail. getter has been customzied ``` From 87d97ac84b1f413c73f9dbd87425394de28efd7e Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Fri, 30 Jun 2017 18:17:29 -0700 Subject: [PATCH 11/14] enrich documentation --- README.md | 88 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 97d720a..3276147 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,23 @@ it('should pass', () => { expect(mockInstance.doSomething).toHaveBeenCalled(); } ``` +## Quick reference +```TypeScript + /* create a mock from a class*/ + const mockInstance1 = MockFactory.create(RealClass); + + /* create a mock from an instance*/ + const mockInstance2 = MockFactory.create(realInstance); + + /* access a function spy */ + const spy1 = mockInstance._spy.functionName._func + + /* access a getter spy */ + const spy2 = mockInstance._spy.propertyName._get + + /* access a setter spy */ + const spy3 = mockInstance._spy.propertyName._set +``` ## Prerequisite @@ -31,8 +48,8 @@ This util is built with and for [Jasmine](https://jasmine.github.io/) test frame This util requires [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and only contains `*.ts` files that must be compiled with a [TypeScript](https://www.typescriptlang.org/) compiler. - ## Usage + ### Install ```Shell npm install jasmine-mock-factory --save-dev @@ -75,77 +92,90 @@ const mockLocation = MockFactory.create(location); ``` ### Using a mock - * `MockFactory.create()` will return an object with the same interface as the real object. You can invoke methods and access properties on this object. + * `MockFactory.create()` will return an object with the same interface as the real object. You can invoke functions and access properties on this object. * In addition, the mock object provides a `_spy` facade, where you can access and config spies on functions and properties. ```TypeScript const mockInstance = MockFactory.create(location); mockInstance.reload(); // use it as if it were the real window.location mockInstance._spy.reload._func; // returns the spy behind location.reload mockInstance._spy.search._get; // returns the spy behind the getter for location.search + mockInstance._spy.hash._set; // returns the spy behind the setter for location.hash ``` #### Invoking functions - * All methods will have a `jasmine.Spy` as the initial value. The spy cannot be overwritten and returns `undefined` by default. + * All functions will have a `jasmine.Spy` as the initial value. The spy cannot be overwritten and returns `undefined` by default. * To access protected and private functions, cast the mockInstance `as any` or use bracket notation. ```TypeScript - mockInstance.publicMethod(42); // the spy behind it is invoked with 42 + mockInstance.publicFunction(42); // the spy behind it is invoked with 42 - (mockInstance as any).privateMethod(42); - mockInstance['privateMethod'](42); + (mockInstance as any).privateFunction(42); + mockInstance['privateFunction'](42); // equivalent ``` #### Spying/stubbing functions * You can change return values of functions or assert their calls by accessing them directly or through the `_spy` facade. * Access a function spy on `mockInstance._spy.functionName._func`. ```TypeScript - mockInstance._spy.publicMethod._func.and.returnValue(42); - (mockInstance.publicMethod as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting + /* stubbing a public function */ + mockInstance._spy.publicFunction._func.and.returnValue(42); + (mockInstance.publicFunction as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting - mockInstance._spy.privateMethod._func.and.returnValue(42); - ((mockInstance as any).privateMethod as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting twice + /* stubbing a private function */ + mockInstance._spy.privateFunction._func.and.returnValue(42); + ((mockInstance as any).privateFunction as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting twice ``` #### Accessing properties * All properties have `undefined` as the initial value. The value can be overwritten with anything. * You have read and write access to any property, even if they were readonly in the real object. - * To read or write value on a protected or private properties, cast the mockInstance `as any` or use bracket notation. - * To write value on a readonly property, cast the mockInstance `as any`. Note that bracket notation won't work. - * By default, modification to the properties will be preserved, even if a getter or setter exists in the real object. + * To read or write a protected or private property, cast the mockInstance `as any` or use bracket notation. + * To write a readonly property, cast the mockInstance `as any`. The bracket notation won't work. + * By default, modification to the properties will persist, even if a getter or setter exists in the real object. ```TypeScript + /* persist modification */ mockInstance.publicProperty = 42; let temp = mockInstance.publicProperty; // temp = 42; + /* access readonly property */ mockInstance.readonlyProperty = 42; // typescript compiler error (mockInstance as any).readonlyProperty = 42; // no problem mockInstance['readonlyProperty'] = 42; // typescript compiler error + /* access private property */ (mockInstance as any).privateProperty = 'foo'; - mockInstance['privateProperty'] = 'foo'; // equivalent to above + mockInstance['privateProperty'] = 'foo'; // equivalent ``` #### Spying/stubbing getters and setters - * All properties have spies on the getter and setter, even if they weren't in the real object. - * Access a getter spy on `mockInstance._spy.property._get`. - * Access a setter spy on `mockInstance._spy.property._set`. - * NOTE: modification to the properties will not be preserved after getter or setter spies are customized - * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions to avoid shooting yourself in the foot. + * All properties have spies on the getter and setter, even if the getter and setter don't exist in the real object. + * Access a getter spy on `mockInstance._spy.propertyName._get`. + * Access a setter spy on `mockInstance._spy.propertyName._set`. + * NOTE: modification to the properties will not persist after getter or setter spies are customized + * NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions carefully to avoid shooting yourself in the foot. ```TypeScript + /* assert getter calls */ let temp = mockInstance.publicProperty; expect(mockInstance._spy.publicProperty._get).toHaveBeenCalled(); + /* assert setter calls on a public property */ mockInstance.publicProperty = 42; expect(mockInstance._spy.publicProperty._set).toHaveBeenCalledWith(42); - expect(mockInstance.publicProperty).toBe(42); // pass - mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */}); // customized setter + + /* customize setter */ + expect(mockInstance.publicProperty).toBe(42); // pass. setter hasn't been customized + mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */}); mockInstance.publicProperty = 100; - expect(mockInstance.publicProperty).toBe(100); // fail. setter has been customized - - mockInstance['privateProperty'] = 100; - expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(100); - expect(mockInstance['privateProperty']).toBe(100); // pass - mockInstance._spy.privateProperty._get.and.returnValue(42); // customized getter - mockInstance['privateProperty'] = 100; - expect(mockInstance['privateProperty']).toBe(100); // fail. getter has been customzied + expect(mockInstance.publicProperty).toBe(100); // fail. expect 42 to be 100. setter was customized + + /* assert setter calls on a private property */ + mockInstance['privateProperty'] = 42; + expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(42); + + /* customize setter */ + expect(mockInstance['privateProperty']).toBe(42); // pass. setter hasn't been customized + mockInstance._spy.privateProperty._get.and.returnValue(100); + mockInstance['privateProperty'] = 42; + expect(mockInstance['privateProperty']).toBe(42); // fail, expect 100 to be 42. getter was customzied ``` ## Develope From 01679c993d03318ee65cff0d965e7a68cef4a997 Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Fri, 30 Jun 2017 18:20:04 -0700 Subject: [PATCH 12/14] update doc --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3276147..ac066a3 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ const mockLocation = MockFactory.create(location); ```TypeScript const mockInstance = MockFactory.create(location); mockInstance.reload(); // use it as if it were the real window.location + let temp = mockInstance.search; // use it as if it were the real window.search + mockInstance.hash = '#myHash'; // use it as if it were the real window.hash mockInstance._spy.reload._func; // returns the spy behind location.reload mockInstance._spy.search._get; // returns the spy behind the getter for location.search mockInstance._spy.hash._set; // returns the spy behind the setter for location.hash From d805531fdd17a8fe525eaa8d48b63e4c30301682 Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Fri, 30 Jun 2017 18:20:29 -0700 Subject: [PATCH 13/14] remove typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac066a3..f9dc795 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ const mockLocation = MockFactory.create(location); const mockInstance = MockFactory.create(location); mockInstance.reload(); // use it as if it were the real window.location let temp = mockInstance.search; // use it as if it were the real window.search - mockInstance.hash = '#myHash'; // use it as if it were the real window.hash + mockInstance.hash = 'myHash'; // use it as if it were the real window.hash mockInstance._spy.reload._func; // returns the spy behind location.reload mockInstance._spy.search._get; // returns the spy behind the getter for location.search mockInstance._spy.hash._set; // returns the spy behind the setter for location.hash From e57ed18ea790cbeb3a8406deab77be3860a6ca81 Mon Sep 17 00:00:00 2001 From: Chuanqi Sun Date: Fri, 30 Jun 2017 18:22:22 -0700 Subject: [PATCH 14/14] fix typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9dc795..ba0cdca 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,8 @@ const mockLocation = MockFactory.create(location); mockInstance['privateProperty'] = 42; expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(42); - /* customize setter */ - expect(mockInstance['privateProperty']).toBe(42); // pass. setter hasn't been customized + /* customize getter */ + expect(mockInstance['privateProperty']).toBe(42); // pass. getter hasn't been customized mockInstance._spy.privateProperty._get.and.returnValue(100); mockInstance['privateProperty'] = 42; expect(mockInstance['privateProperty']).toBe(42); // fail, expect 100 to be 42. getter was customzied