diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 291bf3fc..9b7290fa 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -797,6 +797,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'); @@ -841,7 +848,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); @@ -1082,7 +1089,15 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyFactory = new j$.SpyFactory(); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; + } + + return {}; + }); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { @@ -4919,7 +4934,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)); @@ -4929,7 +4944,8 @@ getJasmineRequireObj().Spy = function (j$) { fn: originalFn, getSpy: function () { return wrapper; - } + }, + customStrategies: customStrategies }), callTracker = new j$.CallTracker(), spy = function () { @@ -5069,11 +5085,11 @@ getJasmineRequireObj().Spy = function (j$) { getJasmineRequireObj().SpyFactory = function(j$) { - function SpyFactory() { + function SpyFactory(getCustomStrategies) { var self = this; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return j$.Spy(name, originalFn, getCustomStrategies()); }; this.createSpyObj = function(baseName, methodNames) { @@ -5264,6 +5280,22 @@ 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] = function() { + var plan = cs[k].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/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index 6804f251..eed14801 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 customStrategy = jasmine.createSpy('custom strategy') + .and.returnValue('custom strategy result'), + factory = jasmine.createSpy('custom strategy factory') + .and.returnValue(customStrategy), + originalFn = jasmine.createSpy('original'), + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + customStrategies: { + doSomething: factory + } + }); + + spyStrategy.doSomething(1, 2, 3); + expect(factory).toHaveBeenCalledWith(1, 2, 3); + expect(spyStrategy.exec(null, ['some', 'args'])) + .toEqual('custom strategy result'); + expect(customStrategy).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..dbfb76ed --- /dev/null +++ b/spec/core/integration/CustomSpyStrategiesSpec.js @@ -0,0 +1,98 @@ +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 strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.describe('suite defining a custom spy strategy', function() { + env.beforeEach(function() { + env.addSpyStrategy('frobnicate', strategyFactory); + }); + + env.it('spec in the suite', function() { + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyFactory).toHaveBeenCalledWith(17); + expect(strategyInstance).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 strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategyFactory); + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyFactory).toHaveBeenCalledWith(17); + expect(strategyInstance).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 strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategyFactory); + var spy = env.createSpy('something') + .and.returnValue('no args return') + .withArgs(1, 2, 3).and.frobnicate(17); + + expect(spy()).toEqual('no args return'); + expect(strategyInstance).not.toHaveBeenCalled(); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyInstance).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(); + }); +}); diff --git a/src/core/Env.js b/src/core/Env.js index 57f6868a..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,7 +400,15 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyFactory = new j$.SpyFactory(); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; + } + + return {}; + }); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { 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 index f36c299e..5d54f9df 100644 --- a/src/core/SpyFactory.js +++ b/src/core/SpyFactory.js @@ -1,10 +1,10 @@ getJasmineRequireObj().SpyFactory = function(j$) { - function SpyFactory() { + function SpyFactory(getCustomStrategies) { var self = this; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return j$.Spy(name, originalFn, getCustomStrategies()); }; this.createSpyObj = function(baseName, methodNames) { diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 643fe73f..6563beb7 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -16,6 +16,22 @@ 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] = function() { + var plan = cs[k].apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; + } + } } /**