UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

295 lines (207 loc) 9.35 kB
/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ /** @class Siesta.Test.BDD.Spy This class implements a "spy" - function wrapper which tracks the calls to itself. Spy can be installed instead of a method in some object or can be used standalone. Note, that spies "belongs" to a spec and once the spec is completed all spies that were installed during it will be removed. */ Class('Siesta.Test.BDD.Spy', { does : [ Siesta.Util.Role.CanGetType ], has : { name : null, processor : { lazy : 'this.buildProcessor' }, hostObject : null, propertyName : null, hasOwnOriginalValue : false, originalValue : null, strategy : 'callThrough', returnValueObj : undefined, fakeFunc : null, throwErrorObj : null, // array of { object : scope, args : [], returnValue : } callsLog : Joose.I.Array, /** * @property {Object} calls This is an object property with several helper methods, related to the calls * tracking information. It is assigned to the wrapper function of spy. * * @property {Function} calls.any Returns `true` if spy was called at least once, `false` otherwise * @property {Function} calls.count Returns the number of times this spy was called * @property {Function} calls.argsFor Accepts an number of the call (0-based) and return an array of arguments * for that call. * @property {Function} calls.allArgs Returns an array with the arguments for every tracked function call. * Every element of the array is, in turn, an array of arguments. * @property {Function} calls.all Returns an array with the context for every tracked function call. * Every element of the array is an object of the following structure: { object : this, args : [ 0, 1, 2 ], returnValue : undefined } * @property {Function} calls.mostRecent Returns a context object of the most-recent tracked function call. * @property {Function} calls.first Returns a context object of the first tracked function call. * @property {Function} calls.reset Reset all tracking data. * * * Example: t.spyOn(obj, 'someMethod').callThrough() obj.someMethod(0, 1) obj.someMethod(1, 2) t.expect(obj.someMethod.calls.any()).toBe(true) t.expect(obj.someMethod.calls.count()).toBe(2) t.expect(obj.someMethod.calls.first()).toEqual({ object : obj, args : [ 0, 1 ], returnValue : undefined }) */ calls : null, t : null, /** * @property {Siesta.Test.BDD.Spy} and This is just a reference to itself, to add some syntax sugar. * * This property is also assigned to the wrapper function of spy. * t.spyOn(obj, 'someMethod').callThrough() // same thing as above t.spyOn(obj, 'someMethod').and.callThrough() // returns spy instance obj.someMethod.and */ and : function () { return this } }, methods : { initialize : function () { var me = this this.calls = { any : function () { return me.callsLog.length > 0 }, count : function () { return me.callsLog.length }, argsFor : function (i) { return me.callsLog[ i ].args }, allArgs : function (i) { return Joose.A.map(me.callsLog, function (call) { return call.args } ) }, all : function () { return me.callsLog }, mostRecent : function () { return me.callsLog[ me.callsLog.length - 1 ] }, first : function () { return me.callsLog[ 0 ] }, reset : function () { me.reset() } } var R = Siesta.Resource('Siesta.Test.BDD.Spy') var hostObject = this.hostObject var propertyName = this.propertyName if (hostObject) { var originalValue = hostObject[ propertyName ] if (!/Function/.test(this.typeOf(originalValue))) throw R.get("spyingNotOnFunction") if (originalValue.__SIESTA_SPY__) originalValue.__SIESTA_SPY__.remove() this.originalValue = hostObject[ propertyName ] this.hasOwnOriginalValue = hostObject.hasOwnProperty(propertyName) hostObject[ propertyName ] = this.getProcessor() } if (this.t) this.t.spies.push(this) }, buildProcessor : function () { var me = this var processor = function () { var args = Array.prototype.slice.call(arguments) var log = { object : this, args : args } me.callsLog.push(log) return log.returnValue = me[ me.strategy + 'Strategy' ](this, args) } processor.__SIESTA_SPY__ = processor.and = me processor.calls = me.calls return processor }, returnValueStrategy : function (obj, args) { return this.returnValueObj }, callThroughStrategy : function (obj, args) { return this.originalValue.apply(obj, args) }, callFakeStrategy : function (obj, args) { return this.fakeFunc.apply(obj, args) }, throwErrorStrategy : function (obj, args) { var error = this.throwErrorObj var ERROR = this.t && this.t.global ? this.t.global.Error : Error if (!(error instanceof ERROR)) error = new ERROR(error) throw error }, /** * This method makes the spy to also execute the original function it has been installed over. The * value returned from original function is returned from the spy. * * @return {Siesta.Test.BDD.Spy} This spy instance */ callThrough : function () { if (!this.hostObject) throw "Need the host object to call through to original method" this.strategy = 'callThrough' return this }, /** * This method makes the spy to just return `undefined` and not execute the original function. * * @return {Siesta.Test.BDD.Spy} This spy instance */ stub : function () { this.returnValue() return this }, /** * This method makes the spy to return the value provided and not execute the original function. * * @param {Object} value The value that will be returned from the spy. * * @return {Siesta.Test.BDD.Spy} This spy instance */ returnValue : function (value) { this.strategy = 'returnValue' this.returnValueObj = value return this }, /** * This method makes the spy to call the provided function and return the value from it, instead of the original function. * * @param {Function} func The function to call instead of the original function * * @return {Siesta.Test.BDD.Spy} This spy instance */ callFake : function (func) { this.strategy = 'callFake' this.fakeFunc = func return this }, /** * This method makes the spy to throw the specified `error` value (instead of calling the original function). * * @param {Object} error The error value to throw. If it is not an `Error` instance, it will be passed to `Error` constructor first. * * @return {Siesta.Test.BDD.Spy} This spy instance */ throwError : function (error) { this.strategy = 'throwError' this.throwErrorObj = error return this }, remove : function () { var hostObject = this.hostObject if (hostObject) { if (this.hasOwnOriginalValue) hostObject[ this.propertyName ] = this.originalValue else delete hostObject[ this.propertyName ] } // cleanup paranoya this.originalValue = this.hostObject = hostObject = null this.callsLog = [] this.returnValueObj = this.fakeFunc = this.throwErrorObj = null var processor = this.getProcessor() if (processor) processor.and = processor.calls = processor.__SIESTA_SPY__ = null this.processor = null }, /** * This method resets all calls tracking data. Spy will report as it has never been called yet. */ reset : function () { this.callsLog = [] } } })