Merge branch 'spyOnProperty2' of https://github.com/celluj34/jasmine into celluj34-spyOnProperty2
- Merges #1203 from @celluj34 - Merges #1008 from @smacker - Fixes #943
This commit is contained in:
@@ -297,6 +297,18 @@ getJasmineRequireObj().util = function() {
|
|||||||
return cloned;
|
return cloned;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
util.getPropertyDescriptor = function(obj, methodName) {
|
||||||
|
var descriptor,
|
||||||
|
proto = obj;
|
||||||
|
|
||||||
|
do {
|
||||||
|
descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
} while (!descriptor && proto);
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
|
||||||
return util;
|
return util;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -794,6 +806,10 @@ getJasmineRequireObj().Env = function(j$) {
|
|||||||
return spyRegistry.spyOn.apply(spyRegistry, arguments);
|
return spyRegistry.spyOn.apply(spyRegistry, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.spyOnProperty = function() {
|
||||||
|
return spyRegistry.spyOnProperty.apply(spyRegistry, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
var suiteFactory = function(description) {
|
var suiteFactory = function(description) {
|
||||||
var suite = new j$.Suite({
|
var suite = new j$.Suite({
|
||||||
env: self,
|
env: self,
|
||||||
@@ -2227,6 +2243,66 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
|||||||
return spiedMethod;
|
return spiedMethod;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.spyOnProperty = function (obj, propertyName, accessType) {
|
||||||
|
accessType = accessType || 'get';
|
||||||
|
|
||||||
|
if (j$.util.isUndefined(obj)) {
|
||||||
|
throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j$.util.isUndefined(propertyName)) {
|
||||||
|
throw new Error('No property name supplied');
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor;
|
||||||
|
try {
|
||||||
|
descriptor = j$.util.getPropertyDescriptor(obj, propertyName);
|
||||||
|
} catch(e) {
|
||||||
|
// IE 8 doesn't support `definePropery` on non-DOM nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!descriptor) {
|
||||||
|
throw new Error(propertyName + ' property does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!descriptor.configurable) {
|
||||||
|
throw new Error(propertyName + ' is not declared configurable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!descriptor[accessType]) {
|
||||||
|
throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j$.isSpy(descriptor[accessType])) {
|
||||||
|
//TODO?: should this return the current spy? Downside: may cause user confusion about spy state
|
||||||
|
throw new Error(propertyName + ' has already been spied upon');
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalDescriptor = j$.util.clone(descriptor),
|
||||||
|
spy = j$.createSpy(propertyName, descriptor[accessType]),
|
||||||
|
restoreStrategy;
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
|
||||||
|
restoreStrategy = function() {
|
||||||
|
Object.defineProperty(obj, propertyName, originalDescriptor);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
restoreStrategy = function() {
|
||||||
|
delete obj[propertyName];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSpies().push({
|
||||||
|
restoreObjectToOriginalState: restoreStrategy
|
||||||
|
});
|
||||||
|
|
||||||
|
descriptor[accessType] = spy;
|
||||||
|
|
||||||
|
Object.defineProperty(obj, propertyName, descriptor);
|
||||||
|
|
||||||
|
return spy;
|
||||||
|
};
|
||||||
|
|
||||||
this.clearSpies = function() {
|
this.clearSpies = function() {
|
||||||
var spies = currentSpies();
|
var spies = currentSpies();
|
||||||
for (var i = spies.length - 1; i >= 0; i--) {
|
for (var i = spies.length - 1; i >= 0; i--) {
|
||||||
@@ -3753,6 +3829,10 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
|||||||
return env.spyOn(obj, methodName);
|
return env.spyOn(obj, methodName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
spyOnProperty: function(obj, methodName, accessType) {
|
||||||
|
return env.spyOnProperty(obj, methodName, accessType);
|
||||||
|
},
|
||||||
|
|
||||||
jsApiReporter: new jasmine.JsApiReporter({
|
jsApiReporter: new jasmine.JsApiReporter({
|
||||||
timer: new jasmine.Timer()
|
timer: new jasmine.Timer()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -77,6 +77,126 @@ describe("SpyRegistry", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#spyOnProperty", function() {
|
||||||
|
// IE 8 doesn't support `definePropery` on non-DOM nodes
|
||||||
|
if (jasmine.getEnv().ieVersion < 9) { return; }
|
||||||
|
|
||||||
|
it("checks for the existence of the object", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry();
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(void 0, 'pants');
|
||||||
|
}).toThrowError(/could not find an object/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks that a property name was passed", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {};
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject);
|
||||||
|
}).toThrowError(/No property name supplied/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks for the existence of the method", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {};
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject, 'pants');
|
||||||
|
}).toThrowError(/property does not exist/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks for the existence of access type", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {};
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'pants', {
|
||||||
|
get: function() { return 1; },
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject, 'pants', 'set');
|
||||||
|
}).toThrowError(/does not have access type/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks if it has already been spied upon", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {};
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'spiedProp', {
|
||||||
|
get: function() { return 1; },
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
spyRegistry.spyOnProperty(subject, 'spiedProp');
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject, 'spiedProp');
|
||||||
|
}).toThrowError(/has already been spied upon/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks if it can be spied upon", function() {
|
||||||
|
var subject = {};
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'myProp', {
|
||||||
|
get: function() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'spiedProp', {
|
||||||
|
get: function() {},
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry();
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject, 'myProp');
|
||||||
|
}).toThrowError(/is not declared configurable/);
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
spyRegistry.spyOnProperty(subject, 'spiedProp');
|
||||||
|
}).not.toThrowError(/is not declared configurable/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides the property getter on the object and returns the spy", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {},
|
||||||
|
returnValue = 1;
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'spiedProperty', {
|
||||||
|
get: function() { return returnValue; },
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(subject.spiedProperty).toEqual(returnValue);
|
||||||
|
|
||||||
|
var spy = spyRegistry.spyOnProperty(subject, 'spiedProperty');
|
||||||
|
var getter = Object.getOwnPropertyDescriptor(subject, 'spiedProperty').get;
|
||||||
|
|
||||||
|
expect(getter).toEqual(spy);
|
||||||
|
expect(subject.spiedProperty).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides the property setter on the object and returns the spy", function() {
|
||||||
|
var spyRegistry = new jasmineUnderTest.SpyRegistry(),
|
||||||
|
subject = {},
|
||||||
|
returnValue = 1;
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'spiedProperty', {
|
||||||
|
get: function() { return returnValue; },
|
||||||
|
set: function() {},
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var spy = spyRegistry.spyOnProperty(subject, 'spiedProperty', 'set');
|
||||||
|
var setter = Object.getOwnPropertyDescriptor(subject, 'spiedProperty').set;
|
||||||
|
|
||||||
|
expect(subject.spiedProperty).toEqual(returnValue);
|
||||||
|
expect(setter).toEqual(spy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#clearSpies", function() {
|
describe("#clearSpies", function() {
|
||||||
it("restores the original functions on the spied-upon objects", function() {
|
it("restores the original functions on the spied-upon objects", function() {
|
||||||
var spies = [],
|
var spies = [],
|
||||||
@@ -152,4 +272,51 @@ describe("SpyRegistry", function() {
|
|||||||
expect(jasmineUnderTest.isSpy(subject.spiedFunc)).toBe(false);
|
expect(jasmineUnderTest.isSpy(subject.spiedFunc)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('spying on properties', function() {
|
||||||
|
it("restores the original properties on the spied-upon objects", function() {
|
||||||
|
// IE 8 doesn't support `definePropery` on non-DOM nodes
|
||||||
|
if (jasmine.getEnv().ieVersion < 9) { return; }
|
||||||
|
|
||||||
|
var spies = [],
|
||||||
|
spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}),
|
||||||
|
originalReturn = 1,
|
||||||
|
subject = {};
|
||||||
|
|
||||||
|
Object.defineProperty(subject, 'spiedProp', {
|
||||||
|
get: function() { return originalReturn; },
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
spyRegistry.spyOnProperty(subject, 'spiedProp');
|
||||||
|
spyRegistry.clearSpies();
|
||||||
|
|
||||||
|
expect(subject.spiedProp).toBe(originalReturn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add a property that the spied-upon object didn't originally have", function() {
|
||||||
|
// IE 8 doesn't support `Object.create`
|
||||||
|
if (jasmine.getEnv().ieVersion < 9) { return; }
|
||||||
|
|
||||||
|
var spies = [],
|
||||||
|
spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}),
|
||||||
|
originalReturn = 1,
|
||||||
|
subjectParent = {};
|
||||||
|
|
||||||
|
Object.defineProperty(subjectParent, 'spiedProp', {
|
||||||
|
get: function() { return originalReturn; },
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var subject = Object.create(subjectParent);
|
||||||
|
|
||||||
|
expect(subject.hasOwnProperty('spiedProp')).toBe(false);
|
||||||
|
|
||||||
|
spyRegistry.spyOnProperty(subject, 'spiedProp');
|
||||||
|
spyRegistry.clearSpies();
|
||||||
|
|
||||||
|
expect(subject.hasOwnProperty('spiedProp')).toBe(false);
|
||||||
|
expect(subject.spiedProp).toBe(originalReturn);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,4 +41,26 @@ describe("jasmineUnderTest.util", function() {
|
|||||||
expect(jasmineUnderTest.util.isUndefined(undefined)).toBe(false);
|
expect(jasmineUnderTest.util.isUndefined(undefined)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getPropertyDescriptor", function() {
|
||||||
|
// IE 8 doesn't support `definePropery` on non-DOM nodes
|
||||||
|
if (jasmine.getEnv().ieVersion < 9) { return; }
|
||||||
|
|
||||||
|
it("get property descriptor from object", function() {
|
||||||
|
var obj = {prop: 1},
|
||||||
|
actual = jasmineUnderTest.util.getPropertyDescriptor(obj, 'prop'),
|
||||||
|
expected = Object.getOwnPropertyDescriptor(obj, 'prop');
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("get property descriptor from object property", function() {
|
||||||
|
var proto = {prop: 1},
|
||||||
|
obj = Object.create(proto),
|
||||||
|
actual = jasmineUnderTest.util.getPropertyDescriptor(proto, 'prop'),
|
||||||
|
expected = Object.getOwnPropertyDescriptor(proto, 'prop');
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -288,6 +288,10 @@ getJasmineRequireObj().Env = function(j$) {
|
|||||||
return spyRegistry.spyOn.apply(spyRegistry, arguments);
|
return spyRegistry.spyOn.apply(spyRegistry, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.spyOnProperty = function() {
|
||||||
|
return spyRegistry.spyOnProperty.apply(spyRegistry, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
var suiteFactory = function(description) {
|
var suiteFactory = function(description) {
|
||||||
var suite = new j$.Suite({
|
var suite = new j$.Suite({
|
||||||
env: self,
|
env: self,
|
||||||
|
|||||||
@@ -68,6 +68,66 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
|||||||
return spiedMethod;
|
return spiedMethod;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.spyOnProperty = function (obj, propertyName, accessType) {
|
||||||
|
accessType = accessType || 'get';
|
||||||
|
|
||||||
|
if (j$.util.isUndefined(obj)) {
|
||||||
|
throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j$.util.isUndefined(propertyName)) {
|
||||||
|
throw new Error('No property name supplied');
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor;
|
||||||
|
try {
|
||||||
|
descriptor = j$.util.getPropertyDescriptor(obj, propertyName);
|
||||||
|
} catch(e) {
|
||||||
|
// IE 8 doesn't support `definePropery` on non-DOM nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!descriptor) {
|
||||||
|
throw new Error(propertyName + ' property does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!descriptor.configurable) {
|
||||||
|
throw new Error(propertyName + ' is not declared configurable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!descriptor[accessType]) {
|
||||||
|
throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j$.isSpy(descriptor[accessType])) {
|
||||||
|
//TODO?: should this return the current spy? Downside: may cause user confusion about spy state
|
||||||
|
throw new Error(propertyName + ' has already been spied upon');
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalDescriptor = j$.util.clone(descriptor),
|
||||||
|
spy = j$.createSpy(propertyName, descriptor[accessType]),
|
||||||
|
restoreStrategy;
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
|
||||||
|
restoreStrategy = function() {
|
||||||
|
Object.defineProperty(obj, propertyName, originalDescriptor);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
restoreStrategy = function() {
|
||||||
|
delete obj[propertyName];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSpies().push({
|
||||||
|
restoreObjectToOriginalState: restoreStrategy
|
||||||
|
});
|
||||||
|
|
||||||
|
descriptor[accessType] = spy;
|
||||||
|
|
||||||
|
Object.defineProperty(obj, propertyName, descriptor);
|
||||||
|
|
||||||
|
return spy;
|
||||||
|
};
|
||||||
|
|
||||||
this.clearSpies = function() {
|
this.clearSpies = function() {
|
||||||
var spies = currentSpies();
|
var spies = currentSpies();
|
||||||
for (var i = spies.length - 1; i >= 0; i--) {
|
for (var i = spies.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
|||||||
return env.spyOn(obj, methodName);
|
return env.spyOn(obj, methodName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
spyOnProperty: function(obj, methodName, accessType) {
|
||||||
|
return env.spyOnProperty(obj, methodName, accessType);
|
||||||
|
},
|
||||||
|
|
||||||
jsApiReporter: new jasmine.JsApiReporter({
|
jsApiReporter: new jasmine.JsApiReporter({
|
||||||
timer: new jasmine.Timer()
|
timer: new jasmine.Timer()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -55,5 +55,17 @@ getJasmineRequireObj().util = function() {
|
|||||||
return cloned;
|
return cloned;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
util.getPropertyDescriptor = function(obj, methodName) {
|
||||||
|
var descriptor,
|
||||||
|
proto = obj;
|
||||||
|
|
||||||
|
do {
|
||||||
|
descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
} while (!descriptor && proto);
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
|
||||||
return util;
|
return util;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user