1 /** @fileOverview 2 A mocking framework for screw-unit. Allows you to temporarily mock out 3 functions with objects you are not testing. 4 5 @author <a href="mailto:topper@toppingdesign.com">Topper Bowers</a> 6 */ 7 8 /** Test helper main namespace 9 @namespace 10 */ 11 var TH = (function () { 12 /** @namespace */ 13 var publicObj = {}; 14 15 /** used to insert a dom mock for the test being run 16 assumes a <div> with an id of "dom_test" and directory 17 of dom_mocks at the same level as suite.html called "dom_mocks" 18 19 @param {String} mock the name of the mock to put into the div (you can skip the html) 20 @param {Object} opts takes an optional insert or a specific element (instead of #dom_test) 21 22 @throws mock must be specified if no mock is specified 23 24 @example 25 TH.insertDomMock("some_mock"); // will insert dom_mocks/some_mock.html into <div id='#dom_test'><div> 26 27 @name TH.insertDomMock 28 @function 29 */ 30 publicObj.insertDomMock = function(mock, opts) { 31 if (!mock) { 32 throw new Error("mock must be specified"); 33 } 34 opts = opts || {}; 35 36 var element = opts.element || jQuery("#dom_test"); 37 38 element = jQuery(element); 39 if (!/\.html$/.test(mock)) { 40 mock += ".html"; 41 } 42 43 var url = "dom_mocks/" + mock; 44 45 var handleResponse = function (html) { 46 if (opts.insert) { 47 element.append(html); 48 } else { 49 element.html(html); 50 } 51 }; 52 53 // use jQuery here so we can mock out Ajax.Request for the tests 54 var ajx = jQuery.ajax({ 55 url: url, 56 async: false, 57 type: 'GET', 58 success: handleResponse 59 }); 60 }; 61 62 /** simulate a browser click on an element passed to the function 63 @param {DomElement} el The element to receive the click 64 65 @name TH.click 66 @function 67 */ 68 publicObj.click = function(el) { 69 if(jQuery.browser.msie) { 70 el.click(); 71 } else { 72 var evt = document.createEvent("MouseEvents"); 73 evt.initEvent("click", true, true); 74 el.dispatchEvent(evt); 75 } 76 }; 77 78 /** pause the operation of a page for X miliseconds 79 @param {Number} millis the number of miliseconds to pause 80 @name TH.pause 81 @function 82 */ 83 publicObj.pause = function (millis) { 84 var date = new Date(); 85 var curDate = null; 86 87 do { curDate = new Date(); } 88 while(curDate-date < millis); 89 }; 90 91 return publicObj; 92 })(); 93 94 /** Mock out objects using a fairly simple interface. Also adds some counting functions. 95 @example 96 var someObj = { 97 foo: function () { return 'bar' } 98 }; 99 someObj.foo() == 'bar'; 100 TH.Mock.Obj("someObj", { 101 foo: function () { return 'somethingElse' } 102 }); 103 someObj.foo() == 'somethingElse'; // BUT! Only for this test the next test will have a normal someObj; 104 105 @example 106 var someObj = { 107 foo: function () { return 'bar' } 108 }; 109 someObj.foo() == 'bar'; 110 TH.Mock.Obj("someObj"); 111 someObj.countCallsOf("foo"); 112 someObj.foo() == true; 113 someObj.numberOfCallsTo("foo") == 1; 114 // using that you can then do cool expectations: expect(someObj.numberOfCallsTo("foo")).to(equal, 1); 115 116 @namespace 117 */ 118 TH.Mock = (function () { 119 /** @namespace */ 120 var publicObj = {}; 121 /** the mocked out objects 122 @name TH.Mock.mockedObjects 123 */ 124 publicObj.mockedObjects = {}; 125 126 /** taken from prototype - bind a function to a certain object 127 @private 128 */ 129 var bind = function(func, obj) { 130 if (obj === undefined) return func; 131 var __method = func, object = obj; 132 return function() { 133 return __method.apply(object, jQuery.makeArray(arguments)); 134 } 135 }; 136 137 /** taken from prototype - copy one object to another 138 @private 139 */ 140 var extendObject = function (destination, source) { 141 for (var property in source) { 142 destination[property] = source[property]; 143 } 144 return destination; 145 }; 146 147 /** this is used as a constructor to make a new mocked object and cache it 148 @private 149 @constructs 150 */ 151 var MockedObject = function (props) { 152 extendObject(this, props); 153 this.countCallsCache = {}; 154 }; 155 156 /** Adds the countCallsOf and numberOfCallsTo methods to any object that 157 is getting mocked 158 @private 159 */ 160 MockedObject.prototype = { 161 countCallsOf: function (propString) { 162 this.countCallsCache[propString] = {}; 163 var prop = this.countCallsCache[propString]; 164 prop.count = 0; 165 this[propString] = bind(function () { 166 this.countCallsCache[propString].count++; 167 }, this); 168 }, 169 numberOfCallsTo: function(propString) { 170 return this.countCallsCache[propString].count; 171 } 172 173 }; 174 175 /** main mocking interface 176 177 @param {String} mockString the string representation of the object you are trying to mock 178 @param {Object} newObj (optional) functions you want to mock on the other object 179 180 @throws if the mockString does not eval into an object 181 182 @see TH.Mock 183 @name TH.Mock.obj 184 @function 185 */ 186 publicObj.obj = function (mockString, newObj) { 187 var oldObj = eval(mockString); 188 var obj; 189 if (!(typeof oldObj == "object")) { 190 throw new Error("TH.Mock.obj called on a string that doesn't evaluate into an object"); 191 } 192 obj = new MockedObject(oldObj); 193 extendObject(obj, newObj); 194 195 publicObj.mockedObjects[mockString] = {}; 196 publicObj.mockedObjects[mockString].newObj = obj; 197 publicObj.mockedObjects[mockString].oldObj = oldObj; 198 199 eval(mockString + " = TH.Mock.mockedObjects['" + mockString + "'].newObj"); 200 201 return obj; 202 }; 203 204 /** Used in a before() to reset all the objects that have been mocked to their original splendor 205 @name TH.Mock.reset 206 @function 207 */ 208 publicObj.reset = function () { 209 var m; 210 var obj; 211 for (mockString in publicObj.mockedObjects) { 212 if (publicObj.mockedObjects.hasOwnProperty(mockString)) { 213 eval(mockString + " = TH.Mock.mockedObjects['" + mockString + "'].oldObj"); 214 } 215 } 216 publicObj.mockedObjects = {}; 217 }; 218 219 /** this will let you call TH.Mock.numberOfCallsTo("name", "prop") - for convenience 220 @name TH.Mock.numberOfCallsTo 221 @function 222 */ 223 publicObj.numberOfCallsTo = function (mockString, propString) { 224 var obj = eval(mockString); 225 return obj.numberOfCallsTo(propString); 226 }; 227 228 // for dev 229 publicObj.dirMocks = function () { 230 console.dir(mockCache); 231 }; 232 233 publicObj.dirCountCalls = function () { 234 console.dir(countCallsCache); 235 }; 236 237 return publicObj; 238 })(); 239 240 /** This mocks out Prototype's ajax calls so that you don't need a server in your tests 241 @example 242 TH.Ajax.mock("/a_url", "someText", 200); 243 var ajx = new Ajax.Request("/a_url", { 244 onComplete: function (resp) { response = resp } 245 }); 246 expect(response.responseText).to(equal, "someText"); 247 248 @namespace 249 */ 250 TH.Ajax = (function () { 251 /** @namespace */ 252 var publicObj = {}; 253 254 var mockAjaxHash = {}; 255 256 /** Lets you count the number of requests on a certain URL 257 @example 258 TH.Ajax.mock("/a_url", "someText", 200); 259 var ajx = new Ajax.Request("/a_url", { 260 onComplete: function (resp) { response = resp } 261 }); 262 expect(TH.Ajax.requestCont["/a_url"]).to(equal, 1); 263 264 @name TH.Ajax.requestCount 265 */ 266 publicObj.requestCount = {}; 267 268 /** Reset the request count - used in a before() 269 @name TH.Ajax.reset 270 @function 271 */ 272 publicObj.reset = function () { 273 publicObj.requestCount = {}; 274 }; 275 276 /** this is the main mocking interface 277 @param {String} urlToMock the url that you want to respond with your response 278 @param {String} response the text you want the server to send back. Text will try to be 279 evaled into JSON so that responseJSON can be set. 280 @param {Number} status (optional) the response code you want the server to send 281 282 @example 283 TH.Ajax.mock("/a_url", "someText", 200); 284 var ajx = new Ajax.Request("/a_url", { 285 onComplete: function (resp) { response = resp } 286 }); 287 expect(response.responseText).to(equal, "someText"); 288 289 @name TH.Ajax.mock 290 @function 291 */ 292 publicObj.mock = function (urlToMock, response, status) { 293 status = status || 200; 294 mockAjaxHash[urlToMock] = { response: response, status: status }; 295 296 Ajax = {}; 297 Ajax.Request = function(url, opts) { 298 if (!mockAjaxHash.hasOwnProperty(url)) { 299 throw new Error("ajax request called with: " + url + " but no mock was found"); 300 } 301 302 if (!publicObj.requestCount[url]) { 303 publicObj.requestCount[url] = 1; 304 } else { 305 publicObj.requestCount[url]++; 306 } 307 308 if(opts.onComplete || opts.onSuccess || opts.onFailure) { 309 response = {}; 310 response.responseText = mockAjaxHash[url].response; 311 response.status = mockAjaxHash[url].status; 312 try { 313 response.responseJSON = response.responseText.evalJSON(); 314 } catch (e) { 315 response.responseJSON = null; 316 } 317 318 if ((response.status == 200) && opts.onSuccess) { 319 opts.onSuccess(response); 320 } else { 321 if (opts.onFailure) { 322 opts.onFailure(response); 323 } 324 } 325 if (opts.onComplete) { 326 opts.onComplete(response); 327 } 328 } 329 }; 330 }; 331 332 return publicObj; 333 })(); 334