Use prototype for spy strategy for better memory management

- Also convert `identity` to a property from a method
This commit is contained in:
Gregg Van Hove
2017-11-30 17:30:20 -08:00
parent a63172f53f
commit 21655a82c9
9 changed files with 115 additions and 119 deletions

View File

@@ -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() {

View File

@@ -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"]);

View File

@@ -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') {

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
};

View File

@@ -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;