From 3847557bbcf0308de99c6fc91e5553e2db016050 Mon Sep 17 00:00:00 2001 From: "Davis W. Frank & Sheel Choksi" Date: Wed, 17 Jul 2013 23:11:55 -0700 Subject: [PATCH] Squashed spy refactor and new spy syntax Jasmine spies now have a 'and' property which allows the user to change the spy's execution strategy-- such as '.and.callReturn(4)' and a 'calls' property which allows inspection of the calls a spy has received. * This is a breaking change * There is a CallTracker that keeps track of all calls and arguments and a SpyStrategy which determines what the spy should do when it is called. --- lib/jasmine-core/jasmine.js | 339 ++++++++++-------- spec/console/ConsoleReporterSpec.js | 4 +- spec/core/CallTrackerSpec.js | 105 ++++++ spec/core/ClockSpec.js | 12 +- spec/core/DelayedFunctionSchedulerSpec.js | 10 +- spec/core/EnvSpec.js | 16 +- spec/core/ExpectationResultSpec.js | 4 +- spec/core/ExpectationSpec.js | 4 +- spec/core/JsApiReporterSpec.js | 2 +- spec/core/PrettyPrintSpec.js | 11 +- spec/core/QueueRunnerSpec.js | 8 +- spec/core/SpecSpec.js | 6 +- spec/core/SpySpec.js | 68 ++-- spec/core/SpyStrategySpec.js | 82 +++++ spec/core/SuiteSpec.js | 2 +- spec/core/TimerSpec.js | 4 +- spec/core/matchers/toContainSpec.js | 4 +- spec/core/matchers/toEqualSpec.js | 4 +- spec/core/matchers/toHaveBeenCalledSpec.js | 8 +- .../core/matchers/toHaveBeenCalledWithSpec.js | 36 +- spec/core/matchers/toThrowErrorSpec.js | 16 +- spec/core/matchers/toThrowSpec.js | 8 +- spec/html/HtmlReporterSpec.js | 2 +- src/core/CallTracker.js | 50 +++ src/core/Env.js | 90 ++--- src/core/PrettyPrinter.js | 13 +- src/core/Spy.js | 86 ----- src/core/SpyStrategy.js | 45 +++ src/core/base.js | 49 ++- src/core/matchers/toHaveBeenCalled.js | 6 +- src/core/matchers/toHaveBeenCalledWith.js | 6 +- src/core/requireCore.js | 5 +- 32 files changed, 692 insertions(+), 413 deletions(-) create mode 100644 spec/core/CallTrackerSpec.js create mode 100644 spec/core/SpyStrategySpec.js create mode 100644 src/core/CallTracker.js delete mode 100644 src/core/Spy.js create mode 100644 src/core/SpyStrategy.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 903eb97d..d9099698 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -35,6 +35,7 @@ getJasmineRequireObj().core = function(jRequire) { jRequire.base(j$); j$.util = jRequire.util(); j$.Any = jRequire.Any(); + j$.CallTracker = jRequire.CallTracker(); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); @@ -44,11 +45,11 @@ getJasmineRequireObj().core = function(jRequire) { j$.JsApiReporter = jRequire.JsApiReporter(); j$.matchersUtil = jRequire.matchersUtil(j$); j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.StringPrettyPrinter = jRequire.StringPrettyPrinter(j$); + j$.pp = jRequire.pp(j$); j$.QueueRunner = jRequire.QueueRunner(); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(); - j$.Spy = jRequire.Spy(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); j$.version = jRequire.version(); @@ -127,12 +128,6 @@ getJasmineRequireObj().base = function(j$) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; - j$.pp = function(value) { - var stringPrettyPrinter = new j$.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; - j$.isDomNode = function(obj) { return obj.nodeType > 0; }; @@ -144,7 +139,48 @@ getJasmineRequireObj().base = function(j$) { j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; - }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + callTracker.track({ + object: this, + args: Array.prototype.slice.apply(arguments) + }); + return spyStrategy.exec.apply(this, arguments); + }; + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw "createSpyObj requires a non-empty array of method names to create spies for"; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; getJasmineRequireObj().util = function() { @@ -307,6 +343,7 @@ if (typeof window == void 0 && typeof exports == "object") { getJasmineRequireObj().Env = function(j$) { function Env(options) { options = options || {}; + var self = this; var global = options.global || j$.getGlobal(); @@ -315,7 +352,8 @@ getJasmineRequireObj().Env = function(j$) { var realSetTimeout = j$.getGlobal().setTimeout; this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); - this.spies_ = []; + var spies = []; + this.currentSpec = null; this.reporter = new j$.ReportDispatcher([ @@ -389,13 +427,13 @@ getJasmineRequireObj().Env = function(j$) { // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; - return buildExpectationResult(attrs); - }; + return buildExpectationResult(attrs); + }; // TODO: fix this naming, and here's where the value comes in this.catchExceptions = function(value) { @@ -407,7 +445,7 @@ getJasmineRequireObj().Env = function(j$) { return catchExceptions; }; - this.catchException = function(e){ + this.catchException = function(e) { return j$.Spec.isPendingSpecException(e) || catchExceptions; }; @@ -458,8 +496,16 @@ getJasmineRequireObj().Env = function(j$) { return spec; + function removeAllSpies() { + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + spies = []; + } + function specResultCallback(result) { - self.removeAllSpies(); + removeAllSpies(); j$.Expectation.resetMatchers(); customEqualityTesters.length = 0; self.clock.uninstall(); @@ -504,6 +550,34 @@ getJasmineRequireObj().Env = function(j$) { }); this.topSuite.execute(self.reporter.jasmineDone); }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (j$.util.isUndefined(obj[methodName])) { + throw methodName + '() method does not exist'; + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw methodName + ' has already been spied upon'; + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + spies.push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; } Env.prototype.addMatchers = function(matchersToAdd) { @@ -518,40 +592,6 @@ getJasmineRequireObj().Env = function(j$) { return this.currentSpec.expect(actual); }; - Env.prototype.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (j$.util.isUndefined(obj[methodName])) { - throw methodName + '() method does not exist'; - } - - if (obj[methodName] && obj[methodName].isSpy) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = j$.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; - }; - - // TODO: move this to closure - Env.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; - }; // TODO: move this to closure Env.prototype.versionString = function() { @@ -754,6 +794,57 @@ getJasmineRequireObj().Any = function() { return Any; }; +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + getJasmineRequireObj().Clock = function() { function Clock(global, delayedFunctionScheduler) { var self = this, @@ -1170,7 +1261,7 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return ObjectContaining; }; -getJasmineRequireObj().StringPrettyPrinter = function(j$) { +getJasmineRequireObj().pp = function(j$) { function PrettyPrinter() { this.ppNestLevel_ = 0; @@ -1190,7 +1281,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar("spy on " + value.identity); + this.emitScalar("spy on " + value.and.identity()); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { @@ -1222,7 +1313,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { if (!obj.hasOwnProperty(property)) continue; if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); + obj.__lookupGetter__(property) !== null) : false); } }; @@ -1236,6 +1327,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { this.string = ''; } + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); StringPrettyPrinter.prototype.emitScalar = function(value) { @@ -1295,7 +1387,11 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { this.string += value; }; - return StringPrettyPrinter; + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; }; getJasmineRequireObj().QueueRunner = function() { @@ -1393,91 +1489,50 @@ getJasmineRequireObj().ReportDispatcher = function() { }; -getJasmineRequireObj().Spy = function(j$) { +getJasmineRequireObj().SpyStrategy = function() { - function Spy(name) { - this.identity = name || 'unknown'; - this.isSpy = true; - this.plan = function() { + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || "unknown", + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; }; - this.mostRecentCall = {}; - this.argsForCall = []; - this.calls = []; + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.callReturn = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.callThrow = function(something) { + plan = function() { + throw something; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; } - Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; - }; - - Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; - }; - - Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; - }; - - Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; - }; - - Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; - }; - - j$.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = j$.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; - }; - - j$.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw "createSpyObj requires a non-empty array of method names to create spies for"; - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; - - return Spy; + return SpyStrategy; }; getJasmineRequireObj().Suite = function() { @@ -1995,11 +2050,11 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); } - result.pass = actual.wasCalled; + result.pass = actual.calls.any(); result.message = result.pass ? - "Expected spy " + actual.identity + " not to have been called." : - "Expected spy " + actual.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; } @@ -2023,13 +2078,13 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } return { - pass: util.contains(actual.argsForCall, expectedArgs) + pass: util.contains(actual.calls.allArgs(), expectedArgs) }; }, message: function(actual) { return { - affirmative: "Expected spy " + actual.identity + " to have been called.", - negative: "Expected spy " + actual.identity + " not to have been called." + affirmative: "Expected spy " + actual.and.identity() + " to have been called.", + negative: "Expected spy " + actual.and.identity() + " not to have been called." }; } }; diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index c8687d6b..5ca843a8 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -90,7 +90,7 @@ describe("ConsoleReporter", function() { reporter.jasmineStarted(); reporter.specDone({status: "passed"}); - timerSpy.elapsed.andReturn(1000); + timerSpy.elapsed.and.callReturn(1000); out.clear(); reporter.jasmineDone(); @@ -127,7 +127,7 @@ describe("ConsoleReporter", function() { out.clear(); - timerSpy.elapsed.andReturn(100); + timerSpy.elapsed.and.callReturn(100); reporter.jasmineDone(); diff --git a/spec/core/CallTrackerSpec.js b/spec/core/CallTrackerSpec.js new file mode 100644 index 00000000..c0028ba5 --- /dev/null +++ b/spec/core/CallTrackerSpec.js @@ -0,0 +1,105 @@ +describe("CallTracker", function() { + it("tracks that it was called when executed", function() { + var callTracker = new j$.CallTracker(); + + expect(callTracker.any()).toBe(false); + + callTracker.track(); + + expect(callTracker.any()).toBe(true); + }); + + it("tracks that number of times that it is executed", function() { + var callTracker = new j$.CallTracker(); + + expect(callTracker.count()).toEqual(0); + + callTracker.track(); + + expect(callTracker.count()).toEqual(1); + }); + + it("tracks the params from each execution", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track({object: void 0, args: []}); + callTracker.track({object: {}, args: [0, "foo"]}); + + expect(callTracker.argsFor(0)).toEqual([]); + + expect(callTracker.argsFor(1)).toEqual([0, "foo"]); + }); + + it("returns any empty array when there was no call", function() { + var callTracker = new j$.CallTracker(); + + expect(callTracker.argsFor(0)).toEqual([]); + }); + + it("allows access for the arguments for all calls", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track({object: {}, args: []}); + callTracker.track({object: {}, args: [0, "foo"]}); + + expect(callTracker.allArgs()).toEqual([[], [0, "foo"]]); + }); + + it("tracks the context and arguments for each call", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track({object: {}, args: []}); + callTracker.track({object: {}, args: [0, "foo"]}); + + expect(callTracker.all()[0]).toEqual({object: {}, args: []}); + + expect(callTracker.all()[1]).toEqual({object: {}, args: [0, "foo"]}); + }); + + it("simplifies access to the arguments for the last (most recent) call", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track(); + callTracker.track({object: {}, args: [0, "foo"]}); + + expect(callTracker.mostRecent()).toEqual({ + object: {}, + args: [0, "foo"] + }); + }); + + it("returns a useful falsy value when there isn't a last (most recent) call", function() { + var callTracker = new j$.CallTracker(); + + expect(callTracker.mostRecent()).toBeFalsy(); + }); + + it("simplifies access to the arguments for the first (oldest) call", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track({object: {}, args: [0, "foo"]}); + + expect(callTracker.first()).toEqual({object: {}, args: [0, "foo"]}) + }); + + it("returns a useful falsy value when there isn't a first (oldest) call", function() { + var callTracker = new j$.CallTracker(); + + expect(callTracker.first()).toBeFalsy(); + }); + + + it("allows the tracking to be reset", function() { + var callTracker = new j$.CallTracker(); + + callTracker.track(); + callTracker.track({object: {}, args: [0, "foo"]}); + callTracker.reset(); + + expect(callTracker.any()).toBe(false); + expect(callTracker.count()).toEqual(0); + expect(callTracker.argsFor(0)).toEqual([]); + expect(callTracker.all()).toEqual([]); + expect(callTracker.mostRecent()).toBeFalsy(); + }); +}); diff --git a/spec/core/ClockSpec.js b/spec/core/ClockSpec.js index 0481e2a1..3e114f0b 100644 --- a/spec/core/ClockSpec.js +++ b/spec/core/ClockSpec.js @@ -31,7 +31,7 @@ describe("Clock", function() { it("returns an id for the delayed function", function() { var setTimeout = jasmine.createSpy('setTimeout'), scheduleId = 123, - scheduleFunction = jasmine.createSpy('scheduleFunction').andReturn(scheduleId), + scheduleFunction = jasmine.createSpy('scheduleFunction').and.callReturn(scheduleId), delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, global = { setTimeout: setTimeout }, delayedFn = jasmine.createSpy('delayedFn'), @@ -100,7 +100,7 @@ describe("Clock", function() { it("returns an id for the delayed function", function() { var setInterval = jasmine.createSpy('setInterval'), scheduleId = 123, - scheduleFunction = jasmine.createSpy('scheduleFunction').andReturn(scheduleId), + scheduleFunction = jasmine.createSpy('scheduleFunction').and.callReturn(scheduleId), delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, global = { setInterval: setInterval }, delayedFn = jasmine.createSpy('delayedFn'), @@ -239,25 +239,25 @@ describe("Clock (acceptance)", function() { clock.tick(50); expect(recurring1).toHaveBeenCalledWith('some', 'other', 'args'); - expect(recurring1.callCount).toBe(1); + expect(recurring1.calls.count()).toBe(1); expect(delayedFn2).not.toHaveBeenCalled(); expect(delayedFn3).not.toHaveBeenCalled(); clock.tick(50); - expect(recurring1.callCount).toBe(2); + expect(recurring1.calls.count()).toBe(2); expect(delayedFn2).toHaveBeenCalled(); expect(delayedFn3).not.toHaveBeenCalled(); clock.tick(100); - expect(recurring1.callCount).toBe(4); + expect(recurring1.calls.count()).toBe(4); expect(delayedFn3).toHaveBeenCalled(); clock.clearInterval(intervalId); clock.tick(50); - expect(recurring1.callCount).toBe(4); + expect(recurring1.calls.count()).toBe(4); }); it("can clear a previously set timeout", function() { diff --git a/spec/core/DelayedFunctionSchedulerSpec.js b/spec/core/DelayedFunctionSchedulerSpec.js index 60ee9c9c..fe5e0acc 100644 --- a/spec/core/DelayedFunctionSchedulerSpec.js +++ b/spec/core/DelayedFunctionSchedulerSpec.js @@ -72,15 +72,15 @@ describe("DelayedFunctionScheduler", function() { scheduler.tick(20); - expect(fn.callCount).toBe(1); + expect(fn.calls.count()).toBe(1); scheduler.tick(40); - expect(fn.callCount).toBe(3); + expect(fn.calls.count()).toBe(3); scheduler.tick(21); - expect(fn.callCount).toBe(4); + expect(fn.calls.count()).toBe(4); }); @@ -158,7 +158,7 @@ describe("DelayedFunctionScheduler", function() { var scheduler = new j$.DelayedFunctionScheduler(), fn = jasmine.createSpy('fn'), recurringCallCount = 0, - recurring = jasmine.createSpy('recurring').andCallFake(function() { + recurring = jasmine.createSpy('recurring').and.callFake(function() { recurringCallCount++; if (recurringCallCount < 5) { expect(fn).not.toHaveBeenCalled(); @@ -171,7 +171,7 @@ describe("DelayedFunctionScheduler", function() { scheduler.tick(60); expect(recurring).toHaveBeenCalled(); - expect(recurring.callCount).toBe(6); + expect(recurring.calls.count()).toBe(6); expect(fn).toHaveBeenCalled(); }); diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index f88d8ca6..133a97a9 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -225,11 +225,11 @@ describe("Env integration", function() { "specDone" ]); - reporter.jasmineDone.andCallFake(function() { + reporter.jasmineDone.and.callFake(function() { expect(reporter.jasmineStarted).toHaveBeenCalledWith({ totalSpecsDefined: 3 }); - var suiteResult = reporter.suiteStarted.calls[0].args[0]; + var suiteResult = reporter.suiteStarted.calls.first().args[0]; expect(suiteResult.description).toEqual("A Suite"); expect(reporter.jasmineDone).toHaveBeenCalled(); @@ -288,9 +288,9 @@ describe("Env integration", function() { "specDone" ]); - reporter.jasmineDone.andCallFake(function() { - var firstSpecResult = reporter.specDone.argsForCall[0][0], - secondSpecResult = reporter.specDone.argsForCall[1][0]; + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.mostRecent().args[0]; expect(firstSpecResult.status).toEqual("passed"); expect(secondSpecResult.status).toEqual("failed"); @@ -355,9 +355,9 @@ describe("Env integration", function() { "specDone" ]); - reporter.jasmineDone.andCallFake(function() { - var firstSpecResult = reporter.specDone.argsForCall[0][0], - secondSpecResult = reporter.specDone.argsForCall[1][0]; + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.mostRecent().args[0]; expect(firstSpecResult.status).toEqual("passed"); expect(secondSpecResult.status).toEqual("failed"); diff --git a/spec/core/ExpectationResultSpec.js b/spec/core/ExpectationResultSpec.js index 00117851..21f4d98c 100644 --- a/spec/core/ExpectationResultSpec.js +++ b/spec/core/ExpectationResultSpec.js @@ -16,7 +16,7 @@ describe("buildExpectationResult", function() { it("delegates message formatting to the provided formatter if there was an Error", function() { var fakeError = {message: 'foo'}, - messageFormatter = jasmine.createSpy("exception message formatter").andReturn(fakeError.message); + messageFormatter = jasmine.createSpy("exception message formatter").and.callReturn(fakeError.message); var result = j$.buildExpectationResult( { @@ -31,7 +31,7 @@ describe("buildExpectationResult", function() { it("delegates stack formatting to the provided formatter if there was an Error", function() { var fakeError = {stack: 'foo'}, - stackFormatter = jasmine.createSpy("stack formatter").andReturn(fakeError.stack); + stackFormatter = jasmine.createSpy("stack formatter").and.callReturn(fakeError.stack); var result = j$.buildExpectationResult( { diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index fe3f5c27..d886e7f4 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -56,7 +56,7 @@ describe("Expectation", function() { it("wraps matchers's compare functions, passing in matcher dependencies", function() { var fakeCompare = function() { return { pass: true }; }, - matcherFactory = jasmine.createSpy("matcher").andReturn({ compare: fakeCompare }), + matcherFactory = jasmine.createSpy("matcher").and.callReturn({ compare: fakeCompare }), matchers = { toFoo: matcherFactory }, @@ -80,7 +80,7 @@ describe("Expectation", function() { }); it("wraps matchers's compare functions, passing the actual and expected", function() { - var fakeCompare = jasmine.createSpy('fake-compare').andReturn({pass: true}), + var fakeCompare = jasmine.createSpy('fake-compare').and.callReturn({pass: true}), matchers = { toFoo: function() { return { diff --git a/spec/core/JsApiReporterSpec.js b/spec/core/JsApiReporterSpec.js index 63ecf23f..47e1e365 100644 --- a/spec/core/JsApiReporterSpec.js +++ b/spec/core/JsApiReporterSpec.js @@ -196,7 +196,7 @@ describe("JsApiReporter", function() { timer: timerSpy }); - timerSpy.elapsed.andReturn(1000); + timerSpy.elapsed.and.callReturn(1000); reporter.jasmineDone(); expect(reporter.executionTime()).toEqual(1000); }); diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 4ad2ce17..2314f926 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -104,13 +104,14 @@ describe("j$.pp", function () { it("should stringify spy objects properly", function() { var TestObject = { - someFunction: function() { - } - }; - spyOn(TestObject, 'someFunction'); + someFunction: function() {} + }, + env = new j$.Env(); + + env.spyOn(TestObject, 'someFunction'); expect(j$.pp(TestObject.someFunction)).toEqual("spy on someFunction"); - expect(j$.pp(jasmine.createSpy("something"))).toEqual("spy on something"); + expect(j$.pp(j$.createSpy("something"))).toEqual("spy on something"); }); it("should stringify objects that implement jasmineToString", function () { diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index bdf7c202..65cb0fa8 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -7,10 +7,10 @@ describe("QueueRunner", function() { queueRunner = new j$.QueueRunner({ fns: [fn1, fn2] }); - fn1.andCallFake(function() { + fn1.and.callFake(function() { calls.push('fn1'); }); - fn2.andCallFake(function() { + fn2.and.callFake(function() { calls.push('fn2'); }); @@ -21,7 +21,7 @@ describe("QueueRunner", function() { it("supports asynchronous functions, only advancing to next function after a done() callback", function() { //TODO: it would be nice if spy arity could match the fake, so we could do something like: - //createSpy('asyncfn').andCallFake(function(done) {}); + //createSpy('asyncfn').and.callFake(function(done) {}); var onComplete = jasmine.createSpy('onComplete'), beforeCallback = jasmine.createSpy('beforeCallback'), @@ -138,6 +138,8 @@ describe("QueueRunner", function() { onComplete: completeCallback }); + clearStack.and.callFake(function(fn) { fn(); }); + queueRunner.execute(); expect(afterFn).toHaveBeenCalled(); expect(clearStack).toHaveBeenCalledWith(completeCallback); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 8ba8a0d9..eae6ad5d 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -54,7 +54,7 @@ describe("Spec", function() { it("should call the start callback on execution but before any befores are called", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), beforesWereCalled = false, - startCallback = jasmine.createSpy('start-callback').andCallFake(function() { + startCallback = jasmine.createSpy('start-callback').and.callFake(function() { expect(beforesWereCalled).toBe(false); }), spec = new j$.Spec({ @@ -77,7 +77,7 @@ describe("Spec", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), before = jasmine.createSpy('before'), after = jasmine.createSpy('after'), - fn = jasmine.createSpy('test body').andCallFake(function() { + fn = jasmine.createSpy('test body').and.callFake(function() { expect(before).toHaveBeenCalled(); expect(after).not.toHaveBeenCalled(); }), @@ -94,7 +94,7 @@ describe("Spec", function() { spec.execute(); - var allSpecFns = fakeQueueRunner.mostRecentCall.args[0].fns; + var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].fns; expect(allSpecFns).toEqual([before, fn, after]); }); diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 0b7fb13b..c11d2836 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -13,20 +13,20 @@ describe('Spies', function () { }; env.spyOn(TestClass, 'someFunction'); - expect(TestClass.someFunction.wasCalled).toEqual(false); - expect(TestClass.someFunction.callCount).toEqual(0); + expect(TestClass.someFunction.calls.any()).toEqual(false); + expect(TestClass.someFunction.calls.count()).toEqual(0); TestClass.someFunction('foo'); - expect(TestClass.someFunction.wasCalled).toEqual(true); - expect(TestClass.someFunction.callCount).toEqual(1); - expect(TestClass.someFunction.mostRecentCall.args).toEqual(['foo']); - expect(TestClass.someFunction.mostRecentCall.object).toEqual(TestClass); + expect(TestClass.someFunction.calls.any()).toEqual(true); + expect(TestClass.someFunction.calls.count()).toEqual(1); + expect(TestClass.someFunction.calls.mostRecent().args).toEqual(['foo']); + expect(TestClass.someFunction.calls.mostRecent().object).toEqual(TestClass); expect(originalFunctionWasCalled).toEqual(false); TestClass.someFunction('bar'); - expect(TestClass.someFunction.callCount).toEqual(2); - expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']); + expect(TestClass.someFunction.calls.count()).toEqual(2); + expect(TestClass.someFunction.calls.mostRecent().args).toEqual(['bar']); }); it('should allow you to view args for a particular call', function() { @@ -40,9 +40,8 @@ describe('Spies', function () { TestClass.someFunction('foo'); TestClass.someFunction('bar'); - expect(TestClass.someFunction.calls[0].args).toEqual(['foo']); - expect(TestClass.someFunction.calls[1].args).toEqual(['bar']); - expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']); + expect(TestClass.someFunction.calls.first().args).toEqual(['foo']); + expect(TestClass.someFunction.calls.mostRecent().args).toEqual(['bar']); }); it('should be possible to call through to the original method, or return a specific result', function() { @@ -58,13 +57,13 @@ describe('Spies', function () { } }; - env.spyOn(TestClass, 'someFunction').andCallThrough(); + env.spyOn(TestClass, 'someFunction').and.callThrough(); var result = TestClass.someFunction('arg1', 'arg2'); expect(result).toEqual("return value from original function"); expect(originalFunctionWasCalled).toEqual(true); expect(passedArgs).toEqual(['arg1', 'arg2']); expect(passedObj).toEqual(TestClass); - expect(TestClass.someFunction.wasCalled).toEqual(true); + expect(TestClass.someFunction.calls.any()).toEqual(true); }); it('should be possible to return a specific value', function() { @@ -76,7 +75,7 @@ describe('Spies', function () { } }; - env.spyOn(TestClass, 'someFunction').andReturn("some value"); + env.spyOn(TestClass, 'someFunction').and.callReturn("some value"); originalFunctionWasCalled = false; var result = TestClass.someFunction('arg1', 'arg2'); expect(result).toEqual("some value"); @@ -92,7 +91,7 @@ describe('Spies', function () { } }; - env.spyOn(TestClass, 'someFunction').andThrow(new Error('fake error')); + env.spyOn(TestClass, 'someFunction').and.callThrow(new Error('fake error')); var exception; try { TestClass.someFunction('arg1', 'arg2'); @@ -115,7 +114,7 @@ describe('Spies', function () { } }; - env.spyOn(TestClass, 'someFunction').andCallFake(function() { + env.spyOn(TestClass, 'someFunction').and.callFake(function() { fakeFunctionWasCalled = true; passedArgs = Array.prototype.slice.call(arguments, 0); passedObj = this; @@ -128,38 +127,19 @@ describe('Spies', function () { expect(fakeFunctionWasCalled).toEqual(true); expect(passedArgs).toEqual(['arg1', 'arg2']); expect(passedObj).toEqual(TestClass); - expect(TestClass.someFunction.wasCalled).toEqual(true); + expect(TestClass.someFunction.calls.any()).toEqual(true); }); - it('is torn down when env.removeAllSpies is called', function() { - var originalFunctionWasCalled = false, - env = new j$.Env(), - TestClass = { - someFunction: function() { - originalFunctionWasCalled = true; - } - }; - env.spyOn(TestClass, 'someFunction'); - - TestClass.someFunction('foo'); - expect(originalFunctionWasCalled).toEqual(false); - - env.removeAllSpies(); - - TestClass.someFunction('foo'); - expect(originalFunctionWasCalled).toEqual(true); - }); - - it('calls removeAllSpies during spec finish', function(done) { + it('removes all spies when env is executed', function(done) { var env = new j$.Env(), originalFoo = function() {}, testObj = { foo: originalFoo }, - firstSpec = jasmine.createSpy('firstSpec').andCallFake(function() { + firstSpec = jasmine.createSpy('firstSpec').and.callFake(function() { env.spyOn(testObj, 'foo'); }), - secondSpec = jasmine.createSpy('secondSpec').andCallFake(function() { + secondSpec = jasmine.createSpy('secondSpec').and.callFake(function() { expect(testObj.foo).toBe(originalFoo); }); env.describe('test suite', function() { @@ -207,14 +187,14 @@ describe('Spies', function () { it('should be able to reset a spy', function() { var TestClass = { someFunction: function() {} }; - env.spyOn(TestClass, 'someFunction'); + spyOn(TestClass, 'someFunction'); expect(TestClass.someFunction).not.toHaveBeenCalled(); TestClass.someFunction(); expect(TestClass.someFunction).toHaveBeenCalled(); - TestClass.someFunction.reset(); + TestClass.someFunction.calls.reset(); expect(TestClass.someFunction).not.toHaveBeenCalled(); - expect(TestClass.someFunction.callCount).toEqual(0); + expect(TestClass.someFunction.calls.count()).toEqual(0); }); describe("createSpyObj", function() { @@ -222,8 +202,8 @@ describe('Spies', function () { var spyObj = j$.createSpyObj('BaseName', ['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); - expect(spyObj.method1.identity).toEqual('BaseName.method1'); - expect(spyObj.method2.identity).toEqual('BaseName.method2'); + expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); + expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); }); it("should throw if you do not pass an array argument", function() { diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js new file mode 100644 index 00000000..4870edb6 --- /dev/null +++ b/spec/core/SpyStrategySpec.js @@ -0,0 +1,82 @@ +describe("SpyStrategy", function() { + + it("defaults its name to unknown", function() { + var spyStrategy = new j$.SpyStrategy(); + + expect(spyStrategy.identity()).toEqual("unknown"); + }); + + it("takes a name", function() { + var spyStrategy = new j$.SpyStrategy({name: "foo"}); + + expect(spyStrategy.identity()).toEqual("foo"); + }); + + it("stubs an original function, if provided", function() { + var originalFn = jasmine.createSpy("original"), + spyStrategy = new j$.SpyStrategy({fn: originalFn}); + + spyStrategy.exec(); + + expect(originalFn).not.toHaveBeenCalled(); + }); + + it("allows an original function to be called, passed through the params and returns it's value", function() { + var originalFn = jasmine.createSpy("original").and.callReturn(42), + spyStrategy = new j$.SpyStrategy({fn: originalFn}), + returnValue; + + spyStrategy.callThrough(); + returnValue = spyStrategy.exec("foo"); + + expect(originalFn).toHaveBeenCalled(); + expect(originalFn.calls.mostRecent().args).toEqual(["foo"]); + expect(returnValue).toEqual(42); + }); + + it("can return a specified value when executed", function() { + var originalFn = jasmine.createSpy("original"), + spyStrategy = new j$.SpyStrategy({fn: originalFn}), + returnValue; + + spyStrategy.callReturn(17); + returnValue = spyStrategy.exec(); + + expect(originalFn).not.toHaveBeenCalled(); + expect(returnValue).toEqual(17); + }); + + it("allows an exception to be thrown when executed", function() { + var originalFn = jasmine.createSpy("original"), + spyStrategy = new j$.SpyStrategy({fn: originalFn}); + + spyStrategy.callThrow("bar"); + + expect(function() { spyStrategy.exec(); }).toThrow("bar"); + expect(originalFn).not.toHaveBeenCalled(); + }); + + it("allows a fake function to be called instead", function() { + var originalFn = jasmine.createSpy("original"), + fakeFn = jasmine.createSpy("fake").and.callReturn(67), + spyStrategy = new j$.SpyStrategy({fn: originalFn}), + returnValue; + + spyStrategy.callFake(fakeFn); + returnValue = spyStrategy.exec(); + + expect(originalFn).not.toHaveBeenCalled(); + expect(returnValue).toEqual(67); + }); + + it("returns the spy after changing the strategy", function(){ + var spy = {}, + spyFn = jasmine.createSpy('spyFn').and.callReturn(spy), + spyStrategy = new j$.SpyStrategy({getSpy: spyFn}); + + expect(spyStrategy.callThrough()).toBe(spy); + expect(spyStrategy.callReturn()).toBe(spy); + expect(spyStrategy.callThrow()).toBe(spy); + expect(spyStrategy.callFake()).toBe(spy); + }); +}); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index fd21a7c3..5820c86d 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -159,7 +159,7 @@ describe("Suite", function() { parentSuite.execute(parentSuiteDone); - var parentSuiteFns = fakeQueueRunnerForParent.mostRecentCall.args[0].fns; + var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].fns; parentSuiteFns[0](); expect(fakeSpec1.execute).toHaveBeenCalled(); diff --git a/spec/core/TimerSpec.js b/spec/core/TimerSpec.js index d9317b20..589ed92a 100644 --- a/spec/core/TimerSpec.js +++ b/spec/core/TimerSpec.js @@ -3,10 +3,10 @@ describe("Timer", function() { var fakeNow = jasmine.createSpy('fake Date.now'), timer = new j$.Timer({now: fakeNow}); - fakeNow.andReturn(100); + fakeNow.and.callReturn(100); timer.start(); - fakeNow.andReturn(200); + fakeNow.and.callReturn(200); expect(timer.elapsed()).toEqual(100); }); diff --git a/spec/core/matchers/toContainSpec.js b/spec/core/matchers/toContainSpec.js index c9443f54..fb3203dd 100644 --- a/spec/core/matchers/toContainSpec.js +++ b/spec/core/matchers/toContainSpec.js @@ -1,7 +1,7 @@ describe("toContain", function() { it("delegates to j$.matchersUtil.contains", function() { var util = { - contains: jasmine.createSpy('delegated-contains').andReturn(true) + contains: jasmine.createSpy('delegated-contains').and.callReturn(true) }, matcher = j$.matchers.toContain(util); @@ -12,7 +12,7 @@ describe("toContain", function() { it("delegates to j$.matchersUtil.contains, passing in equality testers if present", function() { var util = { - contains: jasmine.createSpy('delegated-contains').andReturn(true) + contains: jasmine.createSpy('delegated-contains').and.callReturn(true) }, customEqualityTesters = ['a', 'b'], matcher = j$.matchers.toContain(util, customEqualityTesters); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index d7f4c415..ed7e92a7 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -1,7 +1,7 @@ describe("toEqual", function() { it("delegates to equals function", function() { var util = { - equals: jasmine.createSpy('delegated-equals').andReturn(true) + equals: jasmine.createSpy('delegated-equals').and.callReturn(true) }, matcher = j$.matchers.toEqual(util), result; @@ -14,7 +14,7 @@ describe("toEqual", function() { it("delegates custom equality testers, if present", function() { var util = { - equals: jasmine.createSpy('delegated-equals').andReturn(true) + equals: jasmine.createSpy('delegated-equals').and.callReturn(true) }, customEqualityTesters = ['a', 'b'], matcher = j$.matchers.toEqual(util, customEqualityTesters), diff --git a/spec/core/matchers/toHaveBeenCalledSpec.js b/spec/core/matchers/toHaveBeenCalledSpec.js index 947d0e96..88c8d4a7 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 = j$.matchers.toHaveBeenCalled(), - calledSpy = jasmine.createSpy('called-spy'), + calledSpy = j$.createSpy('called-spy'), result; calledSpy(); @@ -13,7 +13,7 @@ describe("toHaveBeenCalled", function() { it("fails when the actual was not called", function() { var matcher = j$.matchers.toHaveBeenCalled(), - uncalledSpy = jasmine.createSpy('uncalled spy'); + uncalledSpy = j$.createSpy('uncalled spy'); result = matcher.compare(uncalledSpy); expect(result.pass).toBe(false); @@ -28,14 +28,14 @@ describe("toHaveBeenCalled", function() { it("throws an exception when invoked with any arguments", function() { var matcher = j$.matchers.toHaveBeenCalled(), - spy = jasmine.createSpy('sample spy'); + spy = j$.createSpy('sample spy'); expect(function() { matcher.compare(spy, 'foo') }).toThrow(new Error("toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith")); }); it("has a custom message on failure", function() { var matcher = j$.matchers.toHaveBeenCalled(), - spy = jasmine.createSpy('sample-spy'), + spy = j$.createSpy('sample-spy'), result; result = matcher.compare(spy); diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index 5d98479f..3bac22c9 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -1,11 +1,11 @@ describe("toHaveBeenCalledWith", function() { it("passes when the actual was called with matching parameters", function() { var util = { - contains: jasmine.createSpy('delegated-contains').andReturn(true) - }, - matcher = j$.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmine.createSpy('called-spy'), - result; + contains: jasmine.createSpy('delegated-contains').and.callReturn(true) + }, + matcher = j$.matchers.toHaveBeenCalledWith(util), + calledSpy = j$.createSpy('called-spy'), + result; calledSpy('a', 'b'); result = matcher.compare(calledSpy, 'a', 'b'); @@ -15,11 +15,11 @@ describe("toHaveBeenCalledWith", function() { it("fails when the actual was not called", function() { var util = { - contains: jasmine.createSpy('delegated-contains').andReturn(false) - }, - matcher = j$.matchers.toHaveBeenCalledWith(util), - uncalledSpy = jasmine.createSpy('uncalled spy'), - result; + contains: jasmine.createSpy('delegated-contains').and.callReturn(false) + }, + matcher = j$.matchers.toHaveBeenCalledWith(util), + uncalledSpy = j$.createSpy('uncalled spy'), + result; result = matcher.compare(uncalledSpy); expect(result.pass).toBe(false); @@ -27,11 +27,11 @@ describe("toHaveBeenCalledWith", function() { it("fails when the actual was called with different parameters", function() { var util = { - contains: jasmine.createSpy('delegated-contains').andReturn(false) - }, - matcher = j$.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmine.createSpy('called spy'), - result; + contains: jasmine.createSpy('delegated-contains').and.callReturn(false) + }, + matcher = j$.matchers.toHaveBeenCalledWith(util), + calledSpy = j$.createSpy('called spy'), + result; calledSpy('a'); result = matcher.compare(calledSpy, 'a', 'b'); @@ -41,15 +41,15 @@ describe("toHaveBeenCalledWith", function() { it("throws an exception when the actual is not a spy", function() { var matcher = j$.matchers.toHaveBeenCalledWith(), - fn = function() {}; + fn = function() {}; expect(function() { matcher.compare(fn) }).toThrow(new Error("Expected a spy, but got Function.")); }); it("has a custom message on failure", function() { var matcher = j$.matchers.toHaveBeenCalledWith(), - spy = jasmine.createSpy('sample-spy'), - messages = matcher.message(spy); + spy = j$.createSpy('sample-spy'), + messages = matcher.message(spy); expect(messages.affirmative).toEqual("Expected spy sample-spy to have been called.") expect(messages.negative).toEqual("Expected spy sample-spy not to have been called.") diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index 3c65d8c9..ac7eb279 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -144,7 +144,7 @@ describe("toThrowError", function() { it("passes if thrown is an Error and the expected the same Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrowError(util), fn = function() { @@ -160,7 +160,7 @@ describe("toThrowError", function() { it("passes if thrown is a custom error that takes arguments and the expected is the same error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrowError(util), CustomError = function CustomError(arg) { arg.x }, @@ -180,7 +180,7 @@ describe("toThrowError", function() { it("fails if thrown is an Error and the expected is a different Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(false) + equals: jasmine.createSpy('delegated-equal').and.callReturn(false) }, matcher = j$.matchers.toThrowError(util), fn = function() { @@ -196,7 +196,7 @@ describe("toThrowError", function() { it("passes if thrown is a type of Error and it is equal to the expected Error and message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrowError(util), fn = function() { @@ -212,7 +212,7 @@ describe("toThrowError", function() { it("passes if thrown is a custom error that takes arguments and it is equal to the expected custom error and message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrowError(util), CustomError = function CustomError(arg) { this.message = arg.message }, @@ -232,7 +232,7 @@ describe("toThrowError", function() { it("fails if thrown is a type of Error and the expected is a different Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(false) + equals: jasmine.createSpy('delegated-equal').and.callReturn(false) }, matcher = j$.matchers.toThrowError(util), fn = function() { @@ -248,7 +248,7 @@ describe("toThrowError", function() { it("passes if thrown is a type of Error and has the same type as the expected Error and the message matches the exepcted message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrowError(util), fn = function() { @@ -264,7 +264,7 @@ describe("toThrowError", function() { it("fails if thrown is a type of Error and the expected is a different Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(false) + equals: jasmine.createSpy('delegated-equal').and.callReturn(false) }, matcher = j$.matchers.toThrowError(util), fn = function() { diff --git a/spec/core/matchers/toThrowSpec.js b/spec/core/matchers/toThrowSpec.js index ee2c5708..e1e043ea 100644 --- a/spec/core/matchers/toThrowSpec.js +++ b/spec/core/matchers/toThrowSpec.js @@ -22,7 +22,7 @@ describe("toThrow", function() { it("passes if it throws but there is no expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrow(util), fn = function() { @@ -50,7 +50,7 @@ describe("toThrow", function() { it("passes if what is thrown is equivalent to what is expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(true) + equals: jasmine.createSpy('delegated-equal').and.callReturn(true) }, matcher = j$.matchers.toThrow(util), fn = function() { @@ -66,7 +66,7 @@ describe("toThrow", function() { it("fails if what is thrown is not equivalent to what is expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(false) + equals: jasmine.createSpy('delegated-equal').and.callReturn(false) }, matcher = j$.matchers.toThrow(util), fn = function() { @@ -82,7 +82,7 @@ describe("toThrow", function() { it("fails if what is thrown is not equivalent to undefined", function() { var util = { - equals: jasmine.createSpy('delegated-equal').andReturn(false) + equals: jasmine.createSpy('delegated-equal').and.callReturn(false) }, matcher = j$.matchers.toThrow(util), fn = function() { diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index ffcd7cb8..2e9f7a38 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -148,7 +148,7 @@ describe("New HtmlReporter", function() { reporter.jasmineStarted({}); - timer.elapsed.andReturn(100); + timer.elapsed.and.callReturn(100); reporter.jasmineDone(); var duration = container.querySelector(".banner .duration"); diff --git a/src/core/CallTracker.js b/src/core/CallTracker.js new file mode 100644 index 00000000..b80e6a86 --- /dev/null +++ b/src/core/CallTracker.js @@ -0,0 +1,50 @@ +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; diff --git a/src/core/Env.js b/src/core/Env.js index a87891a3..67c0eef8 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1,6 +1,7 @@ getJasmineRequireObj().Env = function(j$) { function Env(options) { options = options || {}; + var self = this; var global = options.global || j$.getGlobal(); @@ -9,7 +10,8 @@ getJasmineRequireObj().Env = function(j$) { var realSetTimeout = j$.getGlobal().setTimeout; this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); - this.spies_ = []; + var spies = []; + this.currentSpec = null; this.reporter = new j$.ReportDispatcher([ @@ -83,13 +85,13 @@ getJasmineRequireObj().Env = function(j$) { // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; - return buildExpectationResult(attrs); - }; + return buildExpectationResult(attrs); + }; // TODO: fix this naming, and here's where the value comes in this.catchExceptions = function(value) { @@ -101,7 +103,7 @@ getJasmineRequireObj().Env = function(j$) { return catchExceptions; }; - this.catchException = function(e){ + this.catchException = function(e) { return j$.Spec.isPendingSpecException(e) || catchExceptions; }; @@ -152,8 +154,16 @@ getJasmineRequireObj().Env = function(j$) { return spec; + function removeAllSpies() { + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + spies = []; + } + function specResultCallback(result) { - self.removeAllSpies(); + removeAllSpies(); j$.Expectation.resetMatchers(); customEqualityTesters.length = 0; self.clock.uninstall(); @@ -198,6 +208,34 @@ getJasmineRequireObj().Env = function(j$) { }); this.topSuite.execute(self.reporter.jasmineDone); }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (j$.util.isUndefined(obj[methodName])) { + throw methodName + '() method does not exist'; + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw methodName + ' has already been spied upon'; + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + spies.push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; } Env.prototype.addMatchers = function(matchersToAdd) { @@ -212,40 +250,6 @@ getJasmineRequireObj().Env = function(j$) { return this.currentSpec.expect(actual); }; - Env.prototype.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (j$.util.isUndefined(obj[methodName])) { - throw methodName + '() method does not exist'; - } - - if (obj[methodName] && obj[methodName].isSpy) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = j$.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; - }; - - // TODO: move this to closure - Env.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; - }; // TODO: move this to closure Env.prototype.versionString = function() { diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 0465ca0a..08d2f41c 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -1,4 +1,4 @@ -getJasmineRequireObj().StringPrettyPrinter = function(j$) { +getJasmineRequireObj().pp = function(j$) { function PrettyPrinter() { this.ppNestLevel_ = 0; @@ -18,7 +18,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar("spy on " + value.identity); + this.emitScalar("spy on " + value.and.identity()); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { @@ -50,7 +50,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { if (!obj.hasOwnProperty(property)) continue; if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); + obj.__lookupGetter__(property) !== null) : false); } }; @@ -64,6 +64,7 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { this.string = ''; } + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); StringPrettyPrinter.prototype.emitScalar = function(value) { @@ -123,5 +124,9 @@ getJasmineRequireObj().StringPrettyPrinter = function(j$) { this.string += value; }; - return StringPrettyPrinter; + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; }; diff --git a/src/core/Spy.js b/src/core/Spy.js deleted file mode 100644 index faf7751d..00000000 --- a/src/core/Spy.js +++ /dev/null @@ -1,86 +0,0 @@ -getJasmineRequireObj().Spy = function(j$) { - - function Spy(name) { - this.identity = name || 'unknown'; - this.isSpy = true; - this.plan = function() { - }; - this.mostRecentCall = {}; - - this.argsForCall = []; - this.calls = []; - } - - Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; - }; - - Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; - }; - - Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; - }; - - Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; - }; - - Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; - }; - - j$.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = j$.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; - }; - - j$.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw "createSpyObj requires a non-empty array of method names to create spies for"; - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; - - return Spy; -}; diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js new file mode 100644 index 00000000..38b25c1c --- /dev/null +++ b/src/core/SpyStrategy.js @@ -0,0 +1,45 @@ +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || "unknown", + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.callReturn = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.callThrow = function(something) { + plan = function() { + throw something; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + } + + return SpyStrategy; +}; diff --git a/src/core/base.js b/src/core/base.js index c0f2c680..19b3a576 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -37,12 +37,6 @@ getJasmineRequireObj().base = function(j$) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; - j$.pp = function(value) { - var stringPrettyPrinter = new j$.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; - j$.isDomNode = function(obj) { return obj.nodeType > 0; }; @@ -54,4 +48,45 @@ getJasmineRequireObj().base = function(j$) { j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; - }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + callTracker.track({ + object: this, + args: Array.prototype.slice.apply(arguments) + }); + return spyStrategy.exec.apply(this, arguments); + }; + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw "createSpyObj requires a non-empty array of method names to create spies for"; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; diff --git a/src/core/matchers/toHaveBeenCalled.js b/src/core/matchers/toHaveBeenCalled.js index 8e96d8bc..a1b647f1 100644 --- a/src/core/matchers/toHaveBeenCalled.js +++ b/src/core/matchers/toHaveBeenCalled.js @@ -13,11 +13,11 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); } - result.pass = actual.wasCalled; + result.pass = actual.calls.any(); result.message = result.pass ? - "Expected spy " + actual.identity + " not to have been called." : - "Expected spy " + actual.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/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index a57688ea..0c655333 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -12,13 +12,13 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } return { - pass: util.contains(actual.argsForCall, expectedArgs) + pass: util.contains(actual.calls.allArgs(), expectedArgs) }; }, message: function(actual) { return { - affirmative: "Expected spy " + actual.identity + " to have been called.", - negative: "Expected spy " + actual.identity + " not to have been called." + affirmative: "Expected spy " + actual.and.identity() + " to have been called.", + negative: "Expected spy " + actual.and.identity() + " not to have been called." }; } }; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index edc3ae1c..caa711eb 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -13,6 +13,7 @@ getJasmineRequireObj().core = function(jRequire) { jRequire.base(j$); j$.util = jRequire.util(); j$.Any = jRequire.Any(); + j$.CallTracker = jRequire.CallTracker(); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); @@ -22,11 +23,11 @@ getJasmineRequireObj().core = function(jRequire) { j$.JsApiReporter = jRequire.JsApiReporter(); j$.matchersUtil = jRequire.matchersUtil(j$); j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.StringPrettyPrinter = jRequire.StringPrettyPrinter(j$); + j$.pp = jRequire.pp(j$); j$.QueueRunner = jRequire.QueueRunner(); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(); - j$.Spy = jRequire.Spy(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); j$.version = jRequire.version();