diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 4a04699f..51b31d90 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -70,6 +70,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); + j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); @@ -305,18 +306,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return new j$.ArrayWithExactContents(sample); }; - /** - * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. - * @name jasmine.createSpy - * @function - * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. - * @param {Function} [originalFn] - Function to act as the real implementation. - * @return {Spy} - */ - j$.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); - }; - j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; @@ -324,47 +313,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker; }; - - /** - * Create an object with multiple {@link Spy}s as its members. - * @name jasmine.createSpyObj - * @function - * @param {String} [baseName] - Base name for the spies in the object. - * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. - * @return {Object} - */ - j$.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; - }; }; getJasmineRequireObj().util = function(j$) { @@ -850,6 +798,13 @@ getJasmineRequireObj().Env = function(j$) { return true; }; + this.addSpyStrategy = function(name, fn) { + if(!currentRunnable()) { + throw new Error('Custom spy strategies must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; + }; + this.addCustomEqualityTester = function(tester) { if(!currentRunnable()) { throw new Error('Custom Equalities must be added in a before function or a spec'); @@ -894,7 +849,7 @@ getJasmineRequireObj().Env = function(j$) { }; var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}, customSpyStrategies: {}}; if(runnableResources[parentRunnableId]){ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); @@ -1135,12 +1090,27 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; } - return runnableResources[currentRunnable().id].spies; - }}); + + return {}; + }); + + var spyRegistry = new j$.SpyRegistry({ + currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }, + createSpy: function(name, originalFn) { + return self.createSpy(name, originalFn); + } + }); this.allowRespy = function(allow){ spyRegistry.allowRespy(allow); @@ -1154,6 +1124,14 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpy = function(name, originalFn) { + return spyFactory.createSpy(name, originalFn); + }; + + this.createSpyObj = function(baseName, methodNames) { + return spyFactory.createSpyObj(baseName, methodNames); + }; + var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn)); @@ -4946,6 +4924,43 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. + * @name jasmine.createSpy + * @function + * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. + * @param {Function} [originalFn] - Function to act as the real implementation. + * @return {Spy} + */ + jasmine.createSpy = function(name, originalFn) { + return env.createSpy(name, originalFn); + }; + + /** + * Create an object with multiple {@link Spy}s as its members. + * @name jasmine.createSpyObj + * @function + * @param {String} [baseName] - Base name for the spies in the object. + * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. + * @return {Object} + */ + jasmine.createSpyObj = function(baseName, methodNames) { + return env.createSpyObj(baseName, methodNames); + }; + + /** + * Add a custom spy strategy for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addSpyStrategy + * @function + * @param {String} name - The name of the strategy (i.e. what you call from `and`) + * @param {Function} factory - Factory function that returns the plan to be executed. + */ + jasmine.addSpyStrategy = function(name, factory) { + return env.addSpyStrategy(identifier, factory); + }; + return jasmineInterface; }; @@ -4964,7 +4979,7 @@ getJasmineRequireObj().Spy = function (j$) { * @constructor * @name Spy */ - function Spy(name, originalFn) { + function Spy(name, originalFn, customStrategies) { var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0), wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); @@ -4974,7 +4989,8 @@ getJasmineRequireObj().Spy = function (j$) { fn: originalFn, getSpy: function () { return wrapper; - } + }, + customStrategies: customStrategies }), callTracker = new j$.CallTracker(), spy = function () { @@ -5112,6 +5128,52 @@ getJasmineRequireObj().Spy = function (j$) { return Spy; }; +getJasmineRequireObj().SpyFactory = function(j$) { + + function SpyFactory(getCustomStrategies) { + var self = this; + + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn, getCustomStrategies()); + }; + + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = self.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + } + + return SpyFactory; +}; + getJasmineRequireObj().SpyRegistry = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'spyOn(, )'); @@ -5119,6 +5181,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { function SpyRegistry(options) { options = options || {}; var global = options.global || j$.getGlobal(); + var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow){ @@ -5154,7 +5217,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalMethod = obj[methodName], - spiedMethod = j$.createSpy(methodName, originalMethod), + spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, methodName) || (obj === global && methodName === 'onerror')) { @@ -5209,7 +5272,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalDescriptor = j$.util.clone(descriptor), - spy = j$.createSpy(propertyName, descriptor[accessType]), + spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { @@ -5263,6 +5326,26 @@ getJasmineRequireObj().SpyStrategy = function(j$) { this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; + + var k, cs = options.customStrategies || {}; + for (k in cs) { + if (j$.util.has(cs, k) && !this[k]) { + this[k] = createCustomPlan(cs[k]); + } + } + } + + function createCustomPlan(factory) { + return function() { + var plan = factory.apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; } /** diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index cfdeeb18..2277afbf 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -255,12 +255,17 @@ describe("jasmineUnderTest.pp", function () { }, env = new jasmineUnderTest.Env(); - var spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() {return [];}}); + var spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() {return [];}, + createSpy: function(name, originalFn) { + return jasmineUnderTest.Spy(name, originalFn); + } + }); spyRegistry.spyOn(TestObject, 'someFunction'); expect(jasmineUnderTest.pp(TestObject.someFunction)).toEqual("spy on someFunction"); - expect(jasmineUnderTest.pp(jasmineUnderTest.createSpy("something"))).toEqual("spy on something"); + expect(jasmineUnderTest.pp(env.createSpy("something"))).toEqual("spy on something"); }); it("should stringify objects that implement jasmineToString", function () { diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js index 3a11cba8..80eac687 100644 --- a/spec/core/SpyRegistrySpec.js +++ b/spec/core/SpyRegistrySpec.js @@ -1,7 +1,11 @@ describe("SpyRegistry", function() { + function createSpy(name, originalFn) { + return jasmineUnderTest.Spy(name, originalFn); + } + describe("#spyOn", function() { it("checks for the existence of the object", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(); + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}); expect(function() { spyRegistry.spyOn(void 0, 'pants'); }).toThrowError(/could not find an object/); @@ -43,7 +47,10 @@ describe("SpyRegistry", function() { it("checks if it has already been spied upon", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), subject = { spiedFunc: function() {} }; spyRegistry.spyOn(subject, 'spiedFunc'); @@ -81,7 +88,7 @@ describe("SpyRegistry", function() { it("overrides the method on the object and returns the spy", function() { var originalFunctionWasCalled = false, - spyRegistry = new jasmineUnderTest.SpyRegistry(), + spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; var spy = spyRegistry.spyOn(subject, 'spiedFunc'); @@ -131,7 +138,7 @@ describe("SpyRegistry", function() { }); it("checks if it has already been spied upon", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}; Object.defineProperty(subject, 'spiedProp', { @@ -170,7 +177,7 @@ describe("SpyRegistry", function() { }); it("overrides the property getter on the object and returns the spy", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}, returnValue = 1; @@ -189,7 +196,7 @@ describe("SpyRegistry", function() { }); it("overrides the property setter on the object and returns the spy", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}, returnValue = 1; @@ -210,7 +217,10 @@ describe("SpyRegistry", function() { describe("#clearSpies", function() { it("restores the original functions on the spied-upon objects", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subject = { spiedFunc: originalFunction }; @@ -222,7 +232,10 @@ describe("SpyRegistry", function() { it("restores the original functions, even when that spy has been replace and re-spied upon", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subject = { spiedFunc: originalFunction }; @@ -241,7 +254,10 @@ describe("SpyRegistry", function() { it("does not add a property that the spied-upon object didn't originally have", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subjectParent = {spiedFunc: originalFunction}; @@ -258,7 +274,10 @@ describe("SpyRegistry", function() { it("restores the original function when it\'s inherited and cannot be deleted", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subjectParent = {spiedFunc: originalFunction}; @@ -298,7 +317,10 @@ describe("SpyRegistry", function() { describe('spying on properties', function() { it("restores the original properties on the spied-upon objects", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalReturn = 1, subject = {}; @@ -315,7 +337,10 @@ describe("SpyRegistry", function() { it("does not add a property that the spied-upon object didn't originally have", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalReturn = 1, subjectParent = {}; diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 562f684b..fb90657f 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -1,4 +1,10 @@ describe('Spies', function () { + var env; + + beforeEach(function() { + env = new jasmineUnderTest.Env(); + }); + describe("createSpy", function() { var TestClass; @@ -9,7 +15,7 @@ describe('Spies', function () { }); it("preserves the properties of the spied function", function() { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); expect(spy.bob).toEqual("test"); }); @@ -18,19 +24,19 @@ describe('Spies', function () { TestClass.prototype.someFunction.and = "turkey"; expect(function() { - jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); }).toThrowError("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"); }); it("adds a spyStrategy and callTracker to the spy", function() { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); expect(spy.and).toEqual(jasmine.any(jasmineUnderTest.SpyStrategy)); expect(spy.calls).toEqual(jasmine.any(jasmineUnderTest.CallTracker)); }); it("tracks the argument of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); spy("arg"); @@ -39,7 +45,7 @@ describe('Spies', function () { }); it("tracks the context of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); var contextObject = { spyMethod: spy }; @@ -49,7 +55,7 @@ describe('Spies', function () { }); it("tracks the return value of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); spy.and.returnValue("return value"); @@ -71,7 +77,7 @@ describe('Spies', function () { for (var arity = 0; arity < functions.length; arity++) { var someFunction = functions[arity], - spy = jasmineUnderTest.createSpy(someFunction.name, someFunction); + spy = env.createSpy(someFunction.name, someFunction); expect(spy.length).toEqual(arity); } @@ -80,7 +86,7 @@ describe('Spies', function () { describe("createSpyObj", function() { it("should create an object with spy methods and corresponding return values when you call jasmine.createSpyObj() with an object", function () { - var spyObj = jasmineUnderTest.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); + var spyObj = env.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); expect(spyObj.method1()).toEqual(42); expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); @@ -91,7 +97,7 @@ describe('Spies', function () { it("should create an object with a bunch of spy methods when you call jasmine.createSpyObj()", function() { - var spyObj = jasmineUnderTest.createSpyObj('BaseName', ['method1', 'method2']); + var spyObj = env.createSpyObj('BaseName', ['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); @@ -99,7 +105,7 @@ describe('Spies', function () { }); it("should allow you to omit the baseName", function() { - var spyObj = jasmineUnderTest.createSpyObj(['method1', 'method2']); + var spyObj = env.createSpyObj(['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); expect(spyObj.method1.and.identity).toEqual('unknown.method1'); @@ -108,25 +114,25 @@ describe('Spies', function () { it("should throw if you do not pass an array or object argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName'); + env.createSpyObj('BaseName'); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); it("should throw if you pass an empty array argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName', []); + env.createSpyObj('BaseName', []); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); it("should throw if you pass an empty object argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName', {}); + env.createSpyObj('BaseName', {}); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); }); it("can use different strategies for different arguments", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.and.returnValue(42); spy.withArgs('baz', 'grault').and.returnValue(-1); spy.withArgs('thud').and.returnValue('bob'); @@ -138,7 +144,7 @@ describe('Spies', function () { }); it("uses custom equality testers when selecting a strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.and.returnValue(42); spy.withArgs(jasmineUnderTest.any(String)).and.returnValue(-1); @@ -147,7 +153,7 @@ describe('Spies', function () { }); it("can reconfigure an argument-specific strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('foo').and.returnValue(42); spy.withArgs('foo').and.returnValue(17); expect(spy('foo')).toEqual(17); @@ -155,14 +161,14 @@ describe('Spies', function () { describe("When withArgs is used without a base strategy", function() { it("uses the matching strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('baz').and.returnValue(-1); expect(spy('baz')).toEqual(-1); }); it("throws if the args don't match", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('bar').and.returnValue(-1); expect(function() { spy('baz', {qux: 42}); }).toThrowError('Spy \'foo\' receieved a call with arguments [ \'baz\', Object({ qux: 42 }) ] but all configured strategies specify other arguments.'); diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index 6804f251..10e4f583 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -110,6 +110,49 @@ describe("SpyStrategy", function() { }) }); + it("allows a custom strategy to be used", function() { + var plan = jasmine.createSpy('custom strategy') + .and.returnValue('custom strategy result'), + customStrategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan), + originalFn = jasmine.createSpy('original'), + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + customStrategies: { + doSomething: customStrategy + } + }); + + spyStrategy.doSomething(1, 2, 3); + expect(customStrategy).toHaveBeenCalledWith(1, 2, 3); + expect(spyStrategy.exec(null, ['some', 'args'])) + .toEqual('custom strategy result'); + expect(plan).toHaveBeenCalledWith('some', 'args'); + }); + + it("throws an error if a custom strategy doesn't return a function", function() { + var originalFn = jasmine.createSpy('original'), + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + customStrategies: { + doSomething: function() { return 'not a function' } + } + }); + + expect(function() { spyStrategy.doSomething(1, 2, 3) }).toThrowError('Spy strategy must return a function'); + }); + + it("does not allow custom strategies to overwrite existing methods", function() { + var spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: function() {}, + customStrategies: { + exec: function() {} + } + }); + + expect(spyStrategy.exec).toBe(jasmineUnderTest.SpyStrategy.prototype.exec); + }); + it('throws an error when a non-function is passed to callFake strategy', function() { var originalFn = jasmine.createSpy('original'), spyStrategy = new jasmineUnderTest.SpyStrategy({fn: originalFn}), diff --git a/spec/core/integration/CustomSpyStrategiesSpec.js b/spec/core/integration/CustomSpyStrategiesSpec.js new file mode 100644 index 00000000..ba82362b --- /dev/null +++ b/spec/core/integration/CustomSpyStrategiesSpec.js @@ -0,0 +1,138 @@ +describe('Custom Spy Strategies (Integration)', function() { + var env; + + beforeEach(function() { + env = new jasmineUnderTest.Env(); + env.randomizeTests(false); + }); + + it('allows adding more strategies local to a suite', function(done) { + var plan = jasmine.createSpy('custom strategy plan') + .and.returnValue(42); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); + + env.describe('suite defining a custom spy strategy', function() { + env.beforeEach(function() { + env.addSpyStrategy('frobnicate', strategy); + }); + + env.it('spec in the suite', function() { + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategy).toHaveBeenCalledWith(17); + expect(plan).toHaveBeenCalledWith(1, 2, 3); + }); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); + + it('allows adding more strategies local to a spec', function(done) { + var plan = jasmine.createSpy('custom strategy plan') + .and.returnValue(42); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategy); + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategy).toHaveBeenCalledWith(17); + expect(plan).toHaveBeenCalledWith(1, 2, 3); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); + + it('allows using custom strategies on a per-argument basis', function(done) { + var plan = jasmine.createSpy('custom strategy plan') + .and.returnValue(42); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategy); + var spy = env.createSpy('something') + .and.returnValue('no args return') + .withArgs(1, 2, 3).and.frobnicate(17); + + expect(spy()).toEqual('no args return'); + expect(plan).not.toHaveBeenCalled(); + expect(spy(1, 2, 3)).toEqual(42); + expect(plan).toHaveBeenCalledWith(1, 2, 3); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); + + it('allows multiple custom strategies to be used', function(done) { + var plan1 = jasmine.createSpy('plan 1').and.returnValue(42), + strategy1 = jasmine.createSpy('strat 1').and.returnValue(plan1), + plan2 = jasmine.createSpy('plan 2').and.returnValue(24), + strategy2 = jasmine.createSpy('strat 2').and.returnValue(plan2), + specDone = jasmine.createSpy('specDone'); + + env.beforeEach(function() { + env.addSpyStrategy('frobnicate', strategy1); + env.addSpyStrategy('jiggle', strategy2); + }); + + env.it('frobnicates', function() { + plan1.calls.reset(); + plan2.calls.reset(); + var spy = env.createSpy('spy').and.frobnicate(); + expect(spy()).toEqual(42); + expect(plan1).toHaveBeenCalled(); + expect(plan2).not.toHaveBeenCalled(); + }); + + env.it('jiggles', function() { + plan1.calls.reset(); + plan2.calls.reset(); + var spy = env.createSpy('spy').and.jiggle(); + expect(spy()).toEqual(24); + expect(plan1).not.toHaveBeenCalled(); + expect(plan2).toHaveBeenCalled(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + expect(specDone.calls.count()).toBe(2); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone, specDone: specDone }); + env.execute(); + }); +}); diff --git a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js index 256ed1d2..f2e89c2f 100644 --- a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js +++ b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js @@ -2,14 +2,14 @@ describe("toHaveBeenCalledBefore", function() { it("throws an exception when the actual is not a spy", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), fn = function() {}, - secondSpy = jasmineUnderTest.createSpy('second spy'); + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); expect(function() { matcher.compare(fn, secondSpy) }).toThrowError(Error, /Expected a spy, but got Function./); }); it("throws an exception when the expected is not a spy", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), fn = function() {}; expect(function() { matcher.compare(firstSpy, fn) }).toThrowError(Error, /Expected a spy, but got Function./); @@ -17,8 +17,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'); + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); secondSpy(); @@ -29,8 +29,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the expected was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'); + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); firstSpy(); @@ -41,8 +41,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual is called after the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; secondSpy(); @@ -55,8 +55,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual is called before and after the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; firstSpy(); @@ -70,8 +70,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the expected is called before and after the actual", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; secondSpy(); @@ -85,8 +85,8 @@ describe("toHaveBeenCalledBefore", function() { it("passes when the actual is called before the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; firstSpy(); diff --git a/spec/core/matchers/toHaveBeenCalledSpec.js b/spec/core/matchers/toHaveBeenCalledSpec.js index e5597176..b14116f0 100644 --- a/spec/core/matchers/toHaveBeenCalledSpec.js +++ b/spec/core/matchers/toHaveBeenCalledSpec.js @@ -1,7 +1,7 @@ describe("toHaveBeenCalled", function() { it("passes when the actual was called, with a custom .not fail message", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy(); @@ -13,7 +13,7 @@ describe("toHaveBeenCalled", function() { it("fails when the actual was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy); @@ -29,14 +29,14 @@ describe("toHaveBeenCalled", function() { it("throws an exception when invoked with any arguments", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - spy = jasmineUnderTest.createSpy('sample spy'); + spy = new jasmineUnderTest.Env().createSpy('sample spy'); expect(function() { matcher.compare(spy, 'foo') }).toThrowError(Error, /Does not take arguments, use toHaveBeenCalledWith/); }); it("has a custom message on failure", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; result = matcher.compare(spy); diff --git a/spec/core/matchers/toHaveBeenCalledTimesSpec.js b/spec/core/matchers/toHaveBeenCalledTimesSpec.js index 6a12e779..62778fbd 100644 --- a/spec/core/matchers/toHaveBeenCalledTimesSpec.js +++ b/spec/core/matchers/toHaveBeenCalledTimesSpec.js @@ -1,14 +1,14 @@ describe("toHaveBeenCalledTimes", function() { it("passes when the actual 0 matches the expected 0 ", function () { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; result = matcher.compare(calledSpy, 0); expect(result.pass).toBeTruthy(); }); it("passes when the actual matches the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy(); @@ -18,7 +18,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when expected numbers is not supplied", function(){ var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('spy'), + spy = new jasmineUnderTest.Env().createSpy('spy'), result; spy(); @@ -29,7 +29,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when the actual was called less than the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy, 2); @@ -38,7 +38,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when the actual was called more than expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; uncalledSpy(); @@ -59,7 +59,7 @@ describe("toHaveBeenCalledTimes", function() { it("has a custom message on failure that tells it was called only once", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; spy(); spy(); @@ -72,7 +72,7 @@ describe("toHaveBeenCalledTimes", function() { it("has a custom message on failure that tells how many times it was called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; spy(); spy(); diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index fa39fc0a..110e3dc2 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -5,7 +5,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(true) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy('a', 'b'); @@ -21,7 +21,7 @@ describe("toHaveBeenCalledWith", function() { }, customEqualityTesters = [function() { return true; }], matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util, customEqualityTesters), - calledSpy = jasmineUnderTest.createSpy('called-spy'); + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'); calledSpy('a', 'b'); matcher.compare(calledSpy, 'a', 'b'); @@ -34,7 +34,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(false) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy); @@ -47,7 +47,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(false) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmineUnderTest.createSpy('called spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; calledSpy('a'); diff --git a/src/core/Env.js b/src/core/Env.js index 003fd8fc..bb116567 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -108,6 +108,13 @@ getJasmineRequireObj().Env = function(j$) { return true; }; + this.addSpyStrategy = function(name, fn) { + if(!currentRunnable()) { + throw new Error('Custom spy strategies must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; + }; + this.addCustomEqualityTester = function(tester) { if(!currentRunnable()) { throw new Error('Custom Equalities must be added in a before function or a spec'); @@ -152,7 +159,7 @@ getJasmineRequireObj().Env = function(j$) { }; var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}, customSpyStrategies: {}}; if(runnableResources[parentRunnableId]){ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); @@ -393,12 +400,27 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; } - return runnableResources[currentRunnable().id].spies; - }}); + + return {}; + }); + + var spyRegistry = new j$.SpyRegistry({ + currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }, + createSpy: function(name, originalFn) { + return self.createSpy(name, originalFn); + } + }); this.allowRespy = function(allow){ spyRegistry.allowRespy(allow); @@ -412,6 +434,14 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpy = function(name, originalFn) { + return spyFactory.createSpy(name, originalFn); + }; + + this.createSpyObj = function(baseName, methodNames) { + return spyFactory.createSpyObj(baseName, methodNames); + }; + var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn)); diff --git a/src/core/Spy.js b/src/core/Spy.js index c99ff191..e6b40669 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -13,7 +13,7 @@ getJasmineRequireObj().Spy = function (j$) { * @constructor * @name Spy */ - function Spy(name, originalFn) { + function Spy(name, originalFn, customStrategies) { var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0), wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); @@ -23,7 +23,8 @@ getJasmineRequireObj().Spy = function (j$) { fn: originalFn, getSpy: function () { return wrapper; - } + }, + customStrategies: customStrategies }), callTracker = new j$.CallTracker(), spy = function () { diff --git a/src/core/SpyFactory.js b/src/core/SpyFactory.js new file mode 100644 index 00000000..d4821321 --- /dev/null +++ b/src/core/SpyFactory.js @@ -0,0 +1,45 @@ +getJasmineRequireObj().SpyFactory = function(j$) { + + function SpyFactory(getCustomStrategies) { + var self = this; + + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn, getCustomStrategies()); + }; + + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = self.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + } + + return SpyFactory; +}; diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js index 7ccb5d3c..eaec419a 100644 --- a/src/core/SpyRegistry.js +++ b/src/core/SpyRegistry.js @@ -5,6 +5,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { function SpyRegistry(options) { options = options || {}; var global = options.global || j$.getGlobal(); + var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow){ @@ -40,7 +41,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalMethod = obj[methodName], - spiedMethod = j$.createSpy(methodName, originalMethod), + spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, methodName) || (obj === global && methodName === 'onerror')) { @@ -95,7 +96,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalDescriptor = j$.util.clone(descriptor), - spy = j$.createSpy(propertyName, descriptor[accessType]), + spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 643fe73f..ae5d4451 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -16,6 +16,26 @@ getJasmineRequireObj().SpyStrategy = function(j$) { this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; + + var k, cs = options.customStrategies || {}; + for (k in cs) { + if (j$.util.has(cs, k) && !this[k]) { + this[k] = createCustomPlan(cs[k]); + } + } + } + + function createCustomPlan(factory) { + return function() { + var plan = factory.apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; } /** diff --git a/src/core/base.js b/src/core/base.js index da39f762..5f0d54aa 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -173,18 +173,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return new j$.ArrayWithExactContents(sample); }; - /** - * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. - * @name jasmine.createSpy - * @function - * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. - * @param {Function} [originalFn] - Function to act as the real implementation. - * @return {Spy} - */ - j$.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); - }; - j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; @@ -192,45 +180,4 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker; }; - - /** - * Create an object with multiple {@link Spy}s as its members. - * @name jasmine.createSpyObj - * @function - * @param {String} [baseName] - Base name for the spies in the object. - * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. - * @return {Object} - */ - j$.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; - }; }; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index b1e19eed..fbc099df 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -48,6 +48,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); + j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index aa79d206..52505ecd 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -256,5 +256,42 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. + * @name jasmine.createSpy + * @function + * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. + * @param {Function} [originalFn] - Function to act as the real implementation. + * @return {Spy} + */ + jasmine.createSpy = function(name, originalFn) { + return env.createSpy(name, originalFn); + }; + + /** + * Create an object with multiple {@link Spy}s as its members. + * @name jasmine.createSpyObj + * @function + * @param {String} [baseName] - Base name for the spies in the object. + * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. + * @return {Object} + */ + jasmine.createSpyObj = function(baseName, methodNames) { + return env.createSpyObj(baseName, methodNames); + }; + + /** + * Add a custom spy strategy for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addSpyStrategy + * @function + * @param {String} name - The name of the strategy (i.e. what you call from `and`) + * @param {Function} factory - Factory function that returns the plan to be executed. + */ + jasmine.addSpyStrategy = function(name, factory) { + return env.addSpyStrategy(identifier, factory); + }; + return jasmineInterface; };