UNPKG

12.9 kBMarkdownView Raw
1**due homage:** [RSpec](http://rspec.info/)
2
3**experimental/unstable** api changes will still occur (**without** deprecation warnings)
4
5`npm install ipso` 0.0.18 [license](./license)
6
7
8Injection Decorator, for mocking and stubbing, with [Mocha](https://github.com/visionmedia/mocha)
9
10Almost all examples in [coffee-script](http://coffeescript.org/).
11
12
13What is this `ipso` thing?
14--------------------------
15
16[The Short Answer](https://github.com/nomilous/vertex/commit/a4b0ef4c6bc14874f5b7d8ff3e5bcbcf4d45edc6)
17
18The Long Answer, ↓
19
20### (test/) Injection Decorator
21
22It is placed in front of the test functions.
23
24```coffee
25ipso = require 'ipso'
26
27it 'does something', ipso (done) ->
28
29 done() # as usual
30
31```
32
33or js:
34
35```js
36ipso = require('ipso');
37
38it('does something', ipso( function(done) {
39
40 done();
41
42} ));
43
44```
45
46It can inject node modules into suites.
47
48```coffee
49
50describe 'it can inject into describe', ipso (vm) ->
51 context 'it can inject into context', ipso (net) ->
52 it 'confirms', ->
53
54 vm.should.equal require 'vm'
55 net.should.equal require 'net'
56
57```
58
59It can inject node modules into tests.
60
61```coffee
62
63it 'does something', ipso (done, http) ->
64
65 http.should.equal require 'http'
66
67```
68
69**IMPORTANT**: `done` will only contain the test resolver if the argument's signaure is literally "done" and it in the first position.
70
71In other words.
72
73```coffee
74
75it 'does something', ipso (finished, http) ->
76
77#
78# => Error: Cannot find module 'finished'
79#
80# And the problem becomes more subtle if there IS a module called 'finshed' installed...
81#
82
83```
84
85
86It defines `.does()` on each injected module for use as a **stubber**.
87
88```coffee
89
90it 'creates an http server', ipso (done, http) ->
91
92 http.does
93 createServer: ->
94 anotherFunction: ->
95
96 http.createServer()
97 done()
98
99```
100
101It uses mocha's JSON diff to display failure to call the stubbed function.
102
103```json
104
105 actual expected
106
107 1 | {
108 2 | "http": {
109 3 | "functions": {
110 4 | "Object.createServer()": "was called"
111 5 | "Object.anotherFunction()": "was NOT called"
112 6 | }
113 7 | }
114 8 | }
115
116```
117
118or, (depending on your mocha version)
119
120```json
121
122 + expected - actual
123
124 {
125 "http": {
126 "functions": {
127 "Object.createServer()": "was called",
128 + "Object.anotherFunction()": "was called"
129 - "Object.anotherFunction()": "was NOT called"
130 }
131 }
132 }
133
134```
135
136The stub replaces the actual function on the module and can therefore return a suitable mock.
137
138```coffee
139http = require 'http'
140class MyServer
141 listen: (opts, handler) ->
142 http.createServer(handler).listen opts.port
143```
144
145```coffee
146{ipso, mock} = require 'ipso'
147
148it 'creates an http server and listens at opts.port', ipso (done, http, MyServer) ->
149
150 http.does
151 createServer: ->
152 return mock('server').does
153 listen: (port) ->
154 port.should.equal 3000
155 done()
156
157 MyServer.listen port: 3000, (req, res) ->
158
159```
160
161You may have noticed that `MyServer` was also injected in the previous example.
162
163* The injector recurses `./lib` and `./app` for the specified module.
164* It does so only if the module has a `CamelCaseModuleName` in the injection argument's signature
165* It searches for the underscored equivalent `./lib/**/*/camel_case_module_name.js|coffee`
166 * TODO: make search strategy configurable
167* These **Local Module Injections** can also be stubbed.
168
169
170It can create multiple function expectation stubs ( **and spies** ).
171
172```coffee
173
174it 'can create multiple expectation stubs', ipso (done, Server) ->
175
176 Server.does
177
178 _listen: ->
179
180 # console.log arguments
181
182 console.log """
183
184 _underscore denotes a spy function
185 ==================================
186
187 * the original will be called after the spy (this function)
188 * both will receive the same arguments
189
190 """
191
192 anotherFunction: ->
193
194 Server.start()
195
196
197```
198
199**IMPORTANT** Stubs set up in before (All) hooks are not enforced as expectations
200
201```coffee
202
203{ipso, mock} = require 'ipso'
204
205
206before ipso ->
207 mock('thing').does
208 function1: -> return 'value1'
209
210
211beforeEach ipso (thing) ->
212
213 #
214 # injected mock thing (as defined in above)
215 #
216
217 thing.does
218 function2: -> return 'value2'
219
220
221
222it 'calls function2', ipso (thing) ->
223
224 thing.function2()
225
226 #
227 # does not fail even tho function1() was not called
228 #
229
230
231```
232
233Mocks can define properties using `.with()`
234
235```coffee
236
237{ipso, mock} = require 'ipso'
238
239before ipso ->
240 mock('thing').with
241 property1: 'value1'
242 property2: 'value2'
243
244beforeEach ipso (thing) ->
245
246 thing.with
247
248 property2: 'overwrite value2'
249
250 .does
251
252 function1: -> 'with and does are chainable'
253 function2: ->
254
255
256```
257
258* Note that `.with()` only exists on objects created with ipso.mock()
259
260
261**PENDING (unlikely, use tags, see below)** It can create future instance stubs (on the prototype)
262
263```coffee
264
265it 'can create multiple expectation stubs', ipso (done, Periscope, events, should) ->
266
267 # Periscope.$prototype.does (dunno yet)
268 Periscope.prototype.does
269
270 measureDepth: -> return 30
271
272 _riseToSurface: (distance, finishedRising) ->
273 distance.should.equal 30
274
275 _openLens: ->
276 @videoStream.codec.should.equal πr²
277
278 #
279 # note: That `@` a.k.a. `this` refers to the instance context
280 # and not the test context. It therefore has access to
281 # properties of the Periscope instance.
282 #
283
284
285 periscope = new Periscope codec: πr²
286 periscope.up (error, eyehole) ->
287
288 should.not.exist error
289 eyehole.should.be.an.instanceof events.EventEmitter
290 done()
291
292```
293
294It supports taging objects for multiple subsequent injections by tag.
295
296```coffee
297
298context 'creates tagged objects for injection into multiple nested tests', ->
299
300 before ipso (ClassName) ->
301
302 ipso.tag
303
304 instanceA: new ClassName 'type A'
305 instanceB: new ClassName 'type B'
306 client: require 'socket.io-client'
307
308 it 'can test with them', (instanceA, instanceB, client) ->
309 it 'and again', (instanceA, instanceB) ->
310
311```
312
313
314### Complex Usage
315
316
317It can create active mocks for fullblown mocking and stubbing
318
319```coffee
320
321beforeEach ipso (done, http) ->
322
323 http.does
324 createServer: (handler) =>
325 process.nextTick ->
326
327 #
328 # mock an actual "hit"
329 #
330
331 handler mock('req'), mock('mock response').does
332
333 writeHead: ->
334 write: ->
335 end: ->
336
337 return ipso.mock( 'mock server' ).does
338
339 listen: (@port, args...) =>
340 address: -> 'mock address object'
341
342 #
343 # note: '=>' pathway from hook's root scope means @port
344 # refers to the `this` of the hook's root scope - which
345 # is shared with the tests themselves, so @port becomes
346 # available in all tests that are preceeded by this hook
347 #
348
349it 'creates a server, starts listening and responds when hit', ipso (facto, http) ->
350
351 server = http.createServer (req, res) ->
352
353 res.writeHead 200
354 res.end()
355 facto()
356
357 server.listen 3000
358 @port.should.equal 3000
359
360```
361```json
362
363 actual expected
364
365 1 | {
366 2 | "http": {
367 3 | "functions": {
368 4 | "Object.createServer()": "was called"
369 5 | }
370 6 | },
371 7 | "mock server": {
372 8 | "functions": {
373 9 | "Object.listen()": "was called",
374 10 | "Object.address()": "was called"
375 11 | }
376 12 | },
377 13 | "mock response": {
378 14 | "functions": {
379 15 | "Object.writeHead()": "was called",
380 16 | "Object.write()": "was NOT called", <--------------------
381 17 | "Object.end()": "was called"
382 18 | }
383 19 | }
384 20 | }
385
386```
387
388
389It can **create** entire module stubs
390
391```coffee
392{ipso, mock, Mock, define} = require 'ipso'
393
394before ipso ->
395
396 #
397 # create a mock to be returned by the module function
398 #
399
400 mock( 'nonExistant' ).with
401
402 function1: ->
403 property1: 'value1'
404
405
406 #
407 # define(listOfFunctions)
408 # -----------------------
409 #
410 # * Keys from the list become module names
411 # * Each function is run by the module stubber
412 # * The returned object is exported as the module
413 #
414
415 define
416
417 #
418 # define a module that exports two class definitions
419 # --------------------------------------------------
420 #
421 # * Mock() (capital 'M') creates mock classes
422 #
423 # * .with() can be used to define a baseset of functions
424 # and property stubs.
425 #
426 # * The mock entity can be injected by tag/name for
427 # per test configuration of function expectations
428 # using .does()
429 #
430
431 missing: ->
432
433 ClassName: Mock 'ClassName'
434 Another: Mock('Another').with(...)
435
436 #
437 # define a module that exports a single function
438 # ----------------------------------------------
439 #
440
441 'non-existant': -> ->
442
443 #
444 # * The second function becomes the exported function of the module.
445 #
446 # * It will be retured by `require 'non-existant'`
447 #
448 # * get() is defined in the module scope to enable reference
449 # to mocks and tags defined in this test scope.
450 #
451
452 return get 'nonExistant'
453
454
455
456it "has created ability to require 'non-existant' in module being tested",
457
458 ipso (nonExistant, SubClass1) ->
459
460 nonExistant.does function2: ->
461 non = require 'non-existant'
462
463 console.log non()
464
465 #
466 # => { function1: [Function],
467 # property1: 'value1',
468 # function2: [Function] }
469 #
470
471
472it "can require 'missing' and create expectations on the Class / instance",
473
474 ipso (ClassName, should) ->
475
476 ClassName.does
477
478 constructor: (arg) -> arg.should.equal 'ARG'
479 someFunction: ->
480
481
482
483
484 #
485 # this would generally be elsewhere (in the module being tested)
486 #
487
488 missing = require 'missing'
489 instance = new missing.ClassName 'ARG'
490 instance.someFunction()
491
492
493```
494
495* Use case
496
497 * Testing [component](http://component.io/) based clientside code without running a browser.
498
499* **IMPORTANT / WARNING**
500
501 * It is a clunky interface and may change drastically.
502 * It tricks `require` into loading the module by tailoring the behaviours
503 of fs.readFileSync, statSync and lstatSync (a not very eloquent method...)
504 * It cannot be reversed (yet), so the stub remains for the duration of the
505 process that created it.
506
507
508it has been shaken, not stirred
509
510
511```coffee
512
513{ipso, tag, define, Mock} = require '../lib/ipso'
514
515before ipso (should) ->
516
517 tag
518
519 Got: should.exist
520 Not: should.not.exist
521
522 define
523
524 martini: -> Mock 'VodkaMartini'
525
526
527it 'has the vodka and the olive', ipso (VodkaMartini, Got, Not) ->
528
529 VodkaMartini.with
530
531 olive: true
532
533 .does
534
535 constructor: -> @vodka = true
536 shake: ->
537
538 Martini = require 'martini'
539 instance = new Martini
540
541 Got instance.vodka
542 Got instance.olive
543 Not instance.gin
544
545 instance.shake()
546
547 try instance.stir()
548
549
550
551 #
552 # ps. there is great value in using **only** local scope in tests... (!, later)
553 #
554
555```
556
557
558It supports promises.
559
560```coffee
561
562it 'fails the test on the first rejection in the chain', ipso (facto, Module) ->
563
564 Module.functionThatReturnsAPromise()
565
566 .then -> Module.functionThatReturnsAPromise()
567 .then -> Module.functionThatReturnsAPromise()
568 .then -> Module.functionThatReturnsAPromise()
569 .then -> facto()
570
571```
572
573Ipso Facto
574
575```coffee
576
577it 'does many things to come', ipso (facto, ...) ->
578
579 facto[MetaThings]()
580
581 #
582 # facto() calls mocha's done() in the background
583 #
584
585```
586
587What MetaThings?
588
589* well, ... (( the brief brainstorm suggested a Planet-sized Plethora of Particularly Peachy Possibilities Perch Patiently Poised Pending a Plunge into **That** rabbit hole.
590
591
592There is a [cli](https://github.com/nomilous/ipso-cli)
593
594* It assists with the overhead of dev using coffee-script, specifically the compile.then -> runTest on changes in src/**/*
595
596
597
598And who is Unthahorsten?
599
600* And why was he doing the equivalent of standing in the equivalent of a laboratory.
601