siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
315 lines (227 loc) • 10.6 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
<span id='Siesta-Test-BDD-Spy'>/**
</span>@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,
<span id='Siesta-Test-BDD-Spy-property-calls'> /**
</span> * @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,
<span id='Siesta-Test-BDD-Spy-property-and'> /**
</span> * @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
},
<span id='Siesta-Test-BDD-Spy-method-callThrough'> /**
</span> * 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
},
<span id='Siesta-Test-BDD-Spy-method-stub'> /**
</span> * 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
},
<span id='Siesta-Test-BDD-Spy-method-returnValue'> /**
</span> * 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
},
<span id='Siesta-Test-BDD-Spy-method-callFake'> /**
</span> * 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
},
<span id='Siesta-Test-BDD-Spy-method-throwError'> /**
</span> * 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
},
<span id='Siesta-Test-BDD-Spy-method-reset'> /**
</span> * This method resets all calls tracking data. Spy will report as it has never been called yet.
*/
reset : function () {
this.callsLog = []
}
}
})
</pre>
</body>
</html>