diff --git a/spec/issues/51.test.ts b/spec/issues/51.test.ts index dd11482..d227b75 100644 --- a/spec/issues/51.test.ts +++ b/spec/issues/51.test.ts @@ -14,8 +14,7 @@ test('issue 51 - All functions shares the same state', async t => { t.is(result, 4); try { calculator.received().divide(1, 2); - t.fail('Expected to have failed.'); } catch (e) { - t.regex(e.toString(), /Expected 1 or more calls to the property divide with no arguments/); + t.regex(e.toString(), /Error: there are no mock for property: divide/); } }); diff --git a/src/Context.ts b/src/Context.ts index 08b4062..ea0b256 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -7,51 +7,51 @@ export class Context { private _proxy: any; private _rootProxy: any; + private _receivedProxy: any; private _state: ContextState; + private _receivedState: ContextState; constructor() { this._initialState = new InitialState(); this._state = this._initialState; this._proxy = new Proxy(() => { }, { - apply: (_target, _this, args) => { - return this.apply(args); - }, - set: (_target, property, value) => { - this.set(property, value); - return true; - }, - get: (_target, property) => { - return this.get(property); - } + apply: (_target, _this, args) => this.apply(_target, _this, args), + set: (_target, property, value) => (this.set(_target, property, value), true), + get: (_target, property) => this.get(_target, property) }); this._rootProxy = new Proxy(() => { }, { - apply: (_target, _this, args) => { - return this.initialState.apply(this, args); - }, - set: (_target, property, value) => { - this.initialState.set(this, property, value); - return true; - }, + apply: (_target, _this, args) => this.initialState.apply(this, args), + set: (_target, property, value) => (this.initialState.set(this, property, value), true), + get: (_target, property) => this.initialState.get(this, property) + }); + + this._receivedProxy = new Proxy(() => { }, { + apply: (_target, _this, args) => this._receivedState.apply(this, args), + set: (_target, property, value) => (this.set(_target, property, value), true), get: (_target, property) => { - return this.initialState.get(this, property); + const state = this.initialState.getPropertyStates.find(getPropertyState => getPropertyState.property === property) + if (state === void 0) throw new Error(`there are no mock for property: ${String(property)}`) + this._receivedState = state + return this.receivedProxy; } }); } - apply(args: any[]) { + apply(_target: any, _this: any, args: any[]) { return this._state.apply(this, args); } - set(property: PropertyKey, value: any) { + set(_target: any, property: PropertyKey, value: any) { return this._state.set(this, property, value); } - get(property: PropertyKey) { - if(property === HandlerKey) + get(_target: any, property: PropertyKey) { + if(property === HandlerKey) { return this; + } return this._state.get(this, property); } @@ -64,6 +64,10 @@ export class Context { return this._rootProxy; } + public get receivedProxy() { + return this._receivedProxy; + } + public get initialState() { return this._initialState; } diff --git a/src/Substitute.ts b/src/Substitute.ts index 57af473..f229837 100644 --- a/src/Substitute.ts +++ b/src/Substitute.ts @@ -1,5 +1,6 @@ import { Context } from "./Context"; import { ObjectSubstitute, OmitProxyMethods, DisabledSubstituteObject } from "./Transformations"; +import { Get } from './Utilities' export const HandlerKey = Symbol(); export const AreProxiesDisabledKey = Symbol(); @@ -13,12 +14,12 @@ export class Substitute { } static disableFor>>(substitute: T): DisabledSubstituteObject { - const thisProxy = substitute as any; - const thisExposedProxy = thisProxy[HandlerKey]; + const thisProxy = substitute as any; // rootProxy + const thisExposedProxy = thisProxy[HandlerKey]; // Context const disableProxy = (f: K): K => { return function() { - thisProxy[AreProxiesDisabledKey] = true; + thisProxy[AreProxiesDisabledKey] = true; // for what reason need to do this? const returnValue = f.call(thisExposedProxy, ...arguments); thisProxy[AreProxiesDisabledKey] = false; return returnValue; @@ -26,9 +27,16 @@ export class Substitute { }; return new Proxy(() => { }, { - apply: disableProxy(thisExposedProxy.apply), - set: disableProxy(thisExposedProxy.set), - get: disableProxy(thisExposedProxy.get) + apply: function (_target, _this, args) { + return disableProxy(thisExposedProxy.apply)(...arguments) + }, + set: function (_target, property, value) { + return disableProxy(thisExposedProxy.set)(...arguments) + }, + get: function (_target, property) { + Get(thisExposedProxy._initialState, thisExposedProxy, property) + return disableProxy(thisExposedProxy.get)(...arguments) + } }) as any; } } \ No newline at end of file diff --git a/src/Utilities.ts b/src/Utilities.ts index ca412b9..6657d45 100644 --- a/src/Utilities.ts +++ b/src/Utilities.ts @@ -1,8 +1,16 @@ import { Argument, AllArguments } from "./Arguments"; +import { GetPropertyState } from './states/GetPropertyState' +import { InitialState } from './states/InitialState' +import { Context } from './Context' import util = require('util') export type Call = any[] // list of args +export enum Type { + method = 'method', + property = 'property' +} + export function stringifyArguments(args: any[]) { args = args.map(x => util.inspect(x)); return args && args.length > 0 ? 'arguments [' + args.join(', ') + ']' : 'no arguments'; @@ -51,4 +59,19 @@ export function areArgumentsEqual(a: any, b: any) { return b.matches(a); return a === b; -}; \ No newline at end of file +}; + +export function Get(recorder: InitialState, context: Context, property: PropertyKey) { + const existingGetState = recorder.getPropertyStates.find(state => state.property === property); + if (existingGetState) { + context.state = existingGetState; + return context.get(void 0, property); + } + + const getState = new GetPropertyState(property); + context.state = getState; + + recorder.recordGetPropertyState(property, getState); + + return context.get(void 0, property); +} \ No newline at end of file diff --git a/src/states/FunctionState.ts b/src/states/FunctionState.ts index 0691b9f..b11efc6 100644 --- a/src/states/FunctionState.ts +++ b/src/states/FunctionState.ts @@ -1,8 +1,7 @@ import { ContextState, PropertyKey } from "./ContextState"; import { Context } from "src/Context"; -import { stringifyArguments, stringifyCalls, areArgumentsEqual, areArgumentArraysEqual, Call } from "../Utilities"; +import { areArgumentArraysEqual, Call, Type } from "../Utilities"; import { GetPropertyState } from "./GetPropertyState"; -import { Argument, Arg } from "../Arguments"; const Nothing = Symbol() @@ -48,7 +47,7 @@ export class FunctionState implements ContextState { context.initialState.assertCallCountMatchesExpectations( this._calls, this.getCallCount(args), - 'method', + Type.method, this.property, args); diff --git a/src/states/GetPropertyState.ts b/src/states/GetPropertyState.ts index ddf8b15..feedfe1 100644 --- a/src/states/GetPropertyState.ts +++ b/src/states/GetPropertyState.ts @@ -1,6 +1,7 @@ import { ContextState, PropertyKey } from "./ContextState"; import { Context } from "src/Context"; import { FunctionState } from "./FunctionState"; +import { Type } from "../Utilities"; const Nothing = Symbol(); @@ -45,7 +46,7 @@ export class GetPropertyState implements ContextState { context.state = functionState; this._functionState = functionState - return context.apply(args); + return context.apply(void 0, void 0, args); } set(context: Context, property: PropertyKey, value: any) { @@ -99,7 +100,7 @@ export class GetPropertyState implements ContextState { context.initialState.assertCallCountMatchesExpectations( [[]], // I'm not sure what this was supposed to mean this.callCount, - 'property', + Type.property, this.property, []); diff --git a/src/states/InitialState.ts b/src/states/InitialState.ts index e1a30cb..5c9d286 100644 --- a/src/states/InitialState.ts +++ b/src/states/InitialState.ts @@ -2,7 +2,7 @@ import { ContextState, PropertyKey } from "./ContextState"; import { Context } from "src/Context"; import { GetPropertyState } from "./GetPropertyState"; import { SetPropertyState } from "./SetPropertyState"; -import { stringifyArguments, stringifyCalls, Call } from "../Utilities"; +import { stringifyArguments, stringifyCalls, Call, Type, Get } from "../Utilities"; import { AreProxiesDisabledKey } from "../Substitute"; export class InitialState implements ContextState { @@ -13,6 +13,8 @@ export class InitialState implements ContextState { private _areProxiesDisabled: boolean; public get expectedCount() { + // expected count of calls, + // being assigned with received() method call return this._expectedCount; } @@ -28,6 +30,14 @@ export class InitialState implements ContextState { return [...this.recordedGetPropertyStates.values()]; } + public recordGetPropertyState(property: PropertyKey, getState: GetPropertyState) { + this.recordedGetPropertyStates.set(property, getState); + } + + public recordSetPropertyState(setState: SetPropertyState) { + this.recordedSetPropertyStates.push(setState); + } + constructor() { this.recordedGetPropertyStates = new Map(); this.recordedSetPropertyStates = []; @@ -36,14 +46,26 @@ export class InitialState implements ContextState { this._expectedCount = void 0; } - assertCallCountMatchesExpectations(calls: Call[], callCount: number, type: string, property: PropertyKey, args: any[]) { + assertCallCountMatchesExpectations( + calls: Call[], // list of arguments + callCount: number, + type: Type, // method or property + property: PropertyKey, + args: any[] + ) { const expectedCount = this._expectedCount; this.clearExpectations(); if(this.doesCallCountMatchExpectations(expectedCount, callCount)) return; - throw new Error('Expected ' + (expectedCount === null ? '1 or more' : expectedCount) + ' call' + (expectedCount === 1 ? '' : 's') + ' to the ' + type + ' ' + property.toString() + ' with ' + stringifyArguments(args) + ', but received ' + (callCount === 0 ? 'none' : callCount) + ' of such call' + (callCount === 1 ? '' : 's') + '.\nAll calls received to ' + type + ' ' + property.toString() + ':' + stringifyCalls(calls)); + throw new Error( + 'Expected ' + (expectedCount === null ? '1 or more' : expectedCount) + + ' call' + (expectedCount === 1 ? '' : 's') + ' to the ' + type + ' ' + property.toString() + + ' with ' + stringifyArguments(args) + ', but received ' + (callCount === 0 ? 'none' : callCount) + + ' of such call' + (callCount === 1 ? '' : 's') + + '.\nAll calls received to ' + type + ' ' + property.toString() + ':' + stringifyCalls(calls) + ); } private doesCallCountMatchExpectations(expectedCount: number|undefined|null, actualCount: number) { @@ -116,22 +138,11 @@ export class InitialState implements ContextState { if (property === 'received') { return (count?: number) => { this._expectedCount = count === void 0 ? null : count; - return context.proxy; + return context.receivedProxy; }; } - const existingGetState = this.recordedGetPropertyStates.get(property); - if (existingGetState) { - context.state = existingGetState; - return context.get(property); - } - - const getState = new GetPropertyState(property); - context.state = getState; - - this.recordedGetPropertyStates.set(property, getState); - - return context.get(property); + return Get(this, context, property) } private clearExpectations() { diff --git a/src/states/SetPropertyState.ts b/src/states/SetPropertyState.ts index 0ce3f9a..44267af 100644 --- a/src/states/SetPropertyState.ts +++ b/src/states/SetPropertyState.ts @@ -1,6 +1,6 @@ import { ContextState, PropertyKey } from "./ContextState"; import { Context } from "src/Context"; -import { areArgumentsEqual } from "../Utilities"; +import { areArgumentsEqual, Type } from "../Utilities"; const Nothing = Symbol(); @@ -27,7 +27,7 @@ export class SetPropertyState implements ContextState { } apply(context: Context): undefined { - return void 0; + throw new Error('Calling apply of setPropertyState is not normal behaviour, something gone wrong') } set(context: Context, property: PropertyKey, value: any) { @@ -44,7 +44,7 @@ export class SetPropertyState implements ContextState { context.initialState.assertCallCountMatchesExpectations( [[]], // not sure what this was supposed to do callCount, - 'property', + Type.property, this.property, this.arguments); @@ -54,6 +54,6 @@ export class SetPropertyState implements ContextState { } get(context: Context, property: PropertyKey): undefined { - return void 0; + throw new Error('Calling get of setPropertyState is not normal behaviour, something gone wrong') } } \ No newline at end of file