From 21655a82c9daf831ee0bf7dab27e758dd2fc0fb7 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Thu, 30 Nov 2017 17:30:20 -0800 Subject: [PATCH] Use prototype for spy strategy for better memory management - Also convert `identity` to a property from a method --- spec/core/SpySpec.js | 12 +- spec/core/SpyStrategySpec.js | 6 +- src/core/PrettyPrinter.js | 2 +- src/core/Spy.js | 2 +- src/core/SpyStrategy.js | 186 ++++++++++---------- src/core/matchers/toHaveBeenCalled.js | 4 +- src/core/matchers/toHaveBeenCalledBefore.js | 12 +- src/core/matchers/toHaveBeenCalledTimes.js | 4 +- src/core/matchers/toHaveBeenCalledWith.js | 6 +- 9 files changed, 115 insertions(+), 119 deletions(-) diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 9bf55c3f..f3266ae5 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -83,10 +83,10 @@ describe('Spies', function () { var spyObj = jasmineUnderTest.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); expect(spyObj.method1()).toEqual(42); - expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); + expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); expect(spyObj.method2()).toEqual('special sauce'); - expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + expect(spyObj.method2.and.identity).toEqual('BaseName.method2'); }); @@ -94,16 +94,16 @@ describe('Spies', function () { var spyObj = jasmineUnderTest.createSpyObj('BaseName', ['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); - expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); - expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); + expect(spyObj.method2.and.identity).toEqual('BaseName.method2'); }); it("should allow you to omit the baseName", function() { var spyObj = jasmineUnderTest.createSpyObj(['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); - expect(spyObj.method1.and.identity()).toEqual('unknown.method1'); - expect(spyObj.method2.and.identity()).toEqual('unknown.method2'); + expect(spyObj.method1.and.identity).toEqual('unknown.method1'); + expect(spyObj.method2.and.identity).toEqual('unknown.method2'); }); it("should throw if you do not pass an array or object argument", function() { diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index 16f1af18..6804f251 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -3,13 +3,13 @@ describe("SpyStrategy", function() { it("defaults its name to unknown", function() { var spyStrategy = new jasmineUnderTest.SpyStrategy(); - expect(spyStrategy.identity()).toEqual("unknown"); + expect(spyStrategy.identity).toEqual("unknown"); }); it("takes a name", function() { var spyStrategy = new jasmineUnderTest.SpyStrategy({name: "foo"}); - expect(spyStrategy.identity()).toEqual("foo"); + expect(spyStrategy.identity).toEqual("foo"); }); it("stubs an original function, if provided", function() { @@ -27,7 +27,7 @@ describe("SpyStrategy", function() { returnValue; spyStrategy.callThrough(); - returnValue = spyStrategy.exec("foo"); + returnValue = spyStrategy.exec(null, ["foo"]); expect(originalFn).toHaveBeenCalled(); expect(originalFn.calls.mostRecent().args).toEqual(["foo"]); diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 16b66074..9adb4d55 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -27,7 +27,7 @@ getJasmineRequireObj().pp = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); + this.emitScalar('spy on ' + value.and.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { diff --git a/src/core/Spy.js b/src/core/Spy.js index 5fd010c7..97fb50ab 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -40,7 +40,7 @@ getJasmineRequireObj().Spy = function (j$) { }; callTracker.track(callData); - var returnValue = spyStrategy.exec.apply(this, arguments); + var returnValue = spyStrategy.exec(this, arguments); callData.returnValue = returnValue; return returnValue; diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index b0c716f7..87a934c7 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -6,105 +6,101 @@ getJasmineRequireObj().SpyStrategy = function(j$) { function SpyStrategy(options) { options = options || {}; - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - /** - * Return the identifying information for the spy. + * Get the identifying information for the spy. * @name Spy#and#identity - * @function - * @returns {String} + * @member + * @type {String} */ - this.identity = function() { - return identity; - }; - - /** - * Execute the current spy strategy. - * @name Spy#and#exec - * @function - */ - this.exec = function() { - return plan.apply(this, arguments); - }; - - /** - * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough - * @function - */ - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - /** - * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue - * @function - * @param {*} value The value to return. - */ - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - /** - * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues - * @function - * @param {...*} values - Values to be returned on subsequent calls to the spy. - */ - this.returnValues = function() { - var values = Array.prototype.slice.call(arguments); - plan = function () { - return values.shift(); - }; - return getSpy(); - }; - - /** - * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError - * @function - * @param {Error|String} something Thing to throw - */ - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - /** - * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake - * @function - * @param {Function} fn The function to invoke with the passed parameters. - */ - this.callFake = function(fn) { - if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { - throw new Error('Argument passed to callFake should be a function, got ' + fn); - } - plan = fn; - return getSpy(); - }; - - /** - * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub - * @function - */ - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; + this.identity = options.name || 'unknown', + this.originalFn = options.fn || function() {}, + this.getSpy = options.getSpy || function() {}, + this.plan = function() {}; } + /** + * Execute the current spy strategy. + * @name Spy#and#exec + * @function + */ + SpyStrategy.prototype.exec = function(context, args) { + return this.plan.apply(context, args); + }; + + /** + * Tell the spy to call through to the real implementation when invoked. + * @name Spy#and#callThrough + * @function + */ + SpyStrategy.prototype.callThrough = function() { + this.plan = this.originalFn; + return this.getSpy(); + }; + + /** + * Tell the spy to return the value when invoked. + * @name Spy#and#returnValue + * @function + * @param {*} value The value to return. + */ + SpyStrategy.prototype.returnValue = function(value) { + this.plan = function() { + return value; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. + * @name Spy#and#returnValues + * @function + * @param {...*} values - Values to be returned on subsequent calls to the spy. + */ + SpyStrategy.prototype.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + this.plan = function () { + return values.shift(); + }; + return this.getSpy(); + }; + + /** + * Tell the spy to throw an error when invoked. + * @name Spy#and#throwError + * @function + * @param {Error|String} something Thing to throw + */ + SpyStrategy.prototype.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + this.plan = function() { + throw error; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to call a fake implementation when invoked. + * @name Spy#and#callFake + * @function + * @param {Function} fn The function to invoke with the passed parameters. + */ + SpyStrategy.prototype.callFake = function(fn) { + if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { + throw new Error('Argument passed to callFake should be a function, got ' + fn); + } + this.plan = fn; + return this.getSpy(); + }; + + /** + * Tell the spy to do nothing when invoked. This is the default. + * @name Spy#and#stub + * @function + */ + SpyStrategy.prototype.stub = function(fn) { + this.plan = function() {}; + return this.getSpy(); + }; + return SpyStrategy; }; diff --git a/src/core/matchers/toHaveBeenCalled.js b/src/core/matchers/toHaveBeenCalled.js index 1612aa70..b6836f45 100644 --- a/src/core/matchers/toHaveBeenCalled.js +++ b/src/core/matchers/toHaveBeenCalled.js @@ -26,8 +26,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { result.pass = actual.calls.any(); result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; + 'Expected spy ' + actual.and.identity + ' not to have been called.' : + 'Expected spy ' + actual.and.identity + ' to have been called.'; return result; } diff --git a/src/core/matchers/toHaveBeenCalledBefore.js b/src/core/matchers/toHaveBeenCalledBefore.js index e507f54c..eb98c07d 100644 --- a/src/core/matchers/toHaveBeenCalledBefore.js +++ b/src/core/matchers/toHaveBeenCalledBefore.js @@ -23,11 +23,11 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { var result = { pass: false }; if (!firstSpy.calls.count()) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.'; return result; } if (!latterSpy.calls.count()) { - result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.'; return result; } @@ -37,17 +37,17 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { result.pass = latest1stSpyCall < first2ndSpyCall; if (result.pass) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy ' + latterSpy.and.identity + ', but it was'; } else { var first1stSpyCall = firstSpy.calls.first().invocationOrder; var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; if(first1stSpyCall < first2ndSpyCall) { - result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)'; } else if (latest2ndSpyCall > latest1stSpyCall) { - result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)'; } else { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' + latterSpy.and.identity; } } diff --git a/src/core/matchers/toHaveBeenCalledTimes.js b/src/core/matchers/toHaveBeenCalledTimes.js index f23a84b3..786b9547 100644 --- a/src/core/matchers/toHaveBeenCalledTimes.js +++ b/src/core/matchers/toHaveBeenCalledTimes.js @@ -29,8 +29,8 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : - 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; + 'Expected spy ' + actual.and.identity + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : + 'Expected spy ' + actual.and.identity + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; diff --git a/src/core/matchers/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index c1399643..0cafcc69 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -23,15 +23,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; return result; } if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; } return result;