1 | **experimental/unstable** api changes will still occur (without deprecation warnings)
|
2 |
|
3 | `npm install ipso` 0.0.10 [license](./license)
|
4 |
|
5 |
|
6 | Decorator, for testing, with [Mocha](https://github.com/visionmedia/mocha)
|
7 |
|
8 | All examples in [coffee-script](http://coffeescript.org/).
|
9 |
|
10 |
|
11 | What is this `ipso` thing?
|
12 | --------------------------
|
13 |
|
14 | [The Short Answer](https://github.com/nomilous/vertex/commit/a4b0ef4c6bc14874f5b7d8ff3e5bcbcf4d45edc6)
|
15 |
|
16 | The Long Answer, ↓
|
17 |
|
18 | ### (test/) Injection Decorator
|
19 |
|
20 | It is placed in front of the test functions.
|
21 |
|
22 | ```coffee
|
23 | ipso = require 'ipso'
|
24 |
|
25 | it 'does something', ipso (done) ->
|
26 |
|
27 | done() # as usual
|
28 |
|
29 | ```
|
30 |
|
31 | It can inject node modules into suites.
|
32 |
|
33 | ```coffee
|
34 |
|
35 | describe 'it can inject into describe', ipso (vm) ->
|
36 | context 'it can inject into context', ipso (net) ->
|
37 | it 'confirms', ->
|
38 |
|
39 | vm.should.equal require 'vm'
|
40 | net.should.equal require 'net'
|
41 |
|
42 | ```
|
43 |
|
44 | It can inject node modules into tests.
|
45 |
|
46 | ```coffee
|
47 |
|
48 | it 'does something', ipso (done, http) ->
|
49 |
|
50 | http.should.equal require 'http'
|
51 |
|
52 | ```
|
53 |
|
54 | **IMPORTANT**: `done` will only contain the test resolver if the argument's signaure is literally "done" and it in the first position.
|
55 |
|
56 | In other words.
|
57 |
|
58 | ```coffee
|
59 |
|
60 | it 'does something', ipso (finished, http) ->
|
61 |
|
62 | #
|
63 | # => Error: Cannot find module 'finished'
|
64 | #
|
65 | # And the problem becomes more subtle if there IS a module called 'finshed' installed...
|
66 | #
|
67 |
|
68 | ```
|
69 |
|
70 |
|
71 | It defines `.does()` on each injected module for use as a **stubber**.
|
72 |
|
73 | ```coffee
|
74 |
|
75 | it 'creates an http server', ipso (done, http) ->
|
76 |
|
77 | http.does
|
78 | createServer: ->
|
79 | anotherFunction: ->
|
80 |
|
81 | http.createServer()
|
82 | done()
|
83 |
|
84 | ```
|
85 |
|
86 | It uses mocha's JSON diff to display failure to call the stubbed function.
|
87 |
|
88 | ```json
|
89 |
|
90 | actual expected
|
91 |
|
92 | 1 | {
|
93 | 2 | "http": {
|
94 | 3 | "functions": {
|
95 | 4 | "Object.createServer()": "was called"
|
96 | 5 | "Object.anotherFunction()": "was NOT called"
|
97 | 6 | }
|
98 | 7 | }
|
99 | 8 | }
|
100 |
|
101 | ```
|
102 |
|
103 | The stub replaces the actual function on the module and can therefore return a suitable mock.
|
104 |
|
105 | ```coffee
|
106 | http = require 'http'
|
107 | class MyServer
|
108 | listen: (opts, handler) ->
|
109 | http.createServer(handler).listen opts.port
|
110 | ```
|
111 |
|
112 | ```coffee
|
113 | {ipso, mock} = require 'ipso'
|
114 |
|
115 | it 'creates an http server and listens at opts.port', ipso (done, http, MyServer) ->
|
116 |
|
117 | http.does
|
118 | createServer: ->
|
119 | return mock('server').does
|
120 | listen: (port) ->
|
121 | port.should.equal 3000
|
122 | done()
|
123 |
|
124 | MyServer.listen port: 3000, (req, res) ->
|
125 |
|
126 | ```
|
127 |
|
128 | You may have noticed that `MyServer` was also injected in the previous example.
|
129 |
|
130 | * The injector recurses `./lib` and `./app` for the specified module.
|
131 | * It does so only if the module has a `CamelCaseModuleName` in the injection argument's signature
|
132 | * It searches for the underscored equivalent `./lib/**/*/camel_case_module_name.js|coffee`
|
133 | * These **Local Module Injections** can also be stubbed.
|
134 |
|
135 |
|
136 | It can create multiple function expectation stubs ( **and spies** ).
|
137 |
|
138 | ```coffee
|
139 |
|
140 | it 'can create multiple expectation stubs', ipso (done, Server) ->
|
141 |
|
142 | Server.does
|
143 |
|
144 | _listen: ->
|
145 |
|
146 | # console.log arguments
|
147 |
|
148 | console.log """
|
149 |
|
150 | _underscore denotes a spy function
|
151 | ==================================
|
152 |
|
153 | * the original will be called after the spy (this function)
|
154 | * both will receive the same arguments
|
155 |
|
156 | """
|
157 |
|
158 | anotherFunction: ->
|
159 |
|
160 | Server.start()
|
161 |
|
162 |
|
163 | ```
|
164 |
|
165 |
|
166 | **PENDING (unlikely, use tags, see below)** It can create future instance stubs (on the prototype)
|
167 |
|
168 | ```coffee
|
169 |
|
170 | it 'can create multiple expectation stubs', ipso (done, Periscope, events, should) ->
|
171 |
|
172 | # Periscope.$prototype.does (dunno yet)
|
173 | Periscope.prototype.does
|
174 |
|
175 | measureDepth: -> return 30
|
176 |
|
177 | _riseToSurface: (distance, finishedRising) ->
|
178 | distance.should.equal 30
|
179 |
|
180 | _openLens: ->
|
181 | @videoStream.codec.should.equal πr²
|
182 |
|
183 | #
|
184 | # note: That `@` a.k.a. `this` refers to the instance context
|
185 | # and not the test context. It therefore has access to
|
186 | # properties of the Periscope instance.
|
187 | #
|
188 |
|
189 |
|
190 | periscope = new Periscope codec: πr²
|
191 | periscope.up (error, eyehole) ->
|
192 |
|
193 | should.not.exist error
|
194 | eyehole.should.be.an.instanceof events.EventEmitter
|
195 | done()
|
196 |
|
197 | ```
|
198 |
|
199 | It supports injection of non-js-eval-able module names or cases where the local module search fails
|
200 |
|
201 |
|
202 | ```coffee
|
203 |
|
204 | ipso = require('ipso').modules
|
205 | engine:
|
206 | require: 'engine.io'
|
207 | Proxy: #
|
208 | require: './lib/proxy/server' # * Because the filenames are the same
|
209 | Core: # so injecting `Server` will fail.
|
210 | require: './lib/core/server' #
|
211 | #
|
212 | ...
|
213 |
|
214 | it 'can inject by config', ipso (done, engine, Proxy, Core) ->
|
215 |
|
216 | #
|
217 | # ...
|
218 | #
|
219 |
|
220 | ```
|
221 | * IMPORTANT
|
222 | * `require` in the above config is a subkey, and **not the require function itself**
|
223 | * The path should be relative to `process.cwd()`, not `__dirname`
|
224 |
|
225 |
|
226 | **PARTIALLY PENDING** It supports tagged objects for multiple subsequent injections.
|
227 |
|
228 | ```coffee
|
229 |
|
230 | context 'creates tagged objects for injection into multiple nested tests', ->
|
231 |
|
232 | before ipso (done, ClassName) ->
|
233 |
|
234 | ipso.tag
|
235 |
|
236 | instanceA: new ClassName 'type A'
|
237 | instanceB: new ClassName 'type B'
|
238 | client: require 'socket.io-client'
|
239 |
|
240 | .then done
|
241 |
|
242 | it 'can test with them', (instanceA, instanceB, client) ->
|
243 | it 'and again', (instanceA, instanceB) ->
|
244 |
|
245 | ```
|
246 |
|
247 |
|
248 | ### Complex Usage
|
249 |
|
250 | It can create active mocks for fullblown mocking and stubbing
|
251 |
|
252 | ```coffee
|
253 |
|
254 | beforeEach ipso (done, http) ->
|
255 |
|
256 | http.does
|
257 | createServer: (handler) =>
|
258 | process.nextTick ->
|
259 |
|
260 | #
|
261 | # mock an actual "hit"
|
262 | #
|
263 |
|
264 | handler mock('req'), mock('mock response').does
|
265 |
|
266 | writeHead: ->
|
267 | write: ->
|
268 | end: ->
|
269 |
|
270 | return ipso.mock( 'mock server' ).does
|
271 |
|
272 | listen: (@port, args...) =>
|
273 | address: -> 'mock address object'
|
274 |
|
275 | #
|
276 | # note: '=>' pathway from hook's root scope means @port
|
277 | # refers to the `this` of the hook's root scope - which
|
278 | # is shared with the tests themselves, so @port becomes
|
279 | # available in all tests that are preceeded by this hook
|
280 | #
|
281 |
|
282 | it 'creates a server, starts listening and responds when hit', ipso (facto, http) ->
|
283 |
|
284 | server = http.createServer (req, res) ->
|
285 |
|
286 | res.writeHead 200
|
287 | res.end()
|
288 | facto()
|
289 |
|
290 | server.listen 3000
|
291 | @port.should.equal 3000
|
292 |
|
293 | ```
|
294 | ```json
|
295 |
|
296 | actual expected
|
297 |
|
298 | 1 | {
|
299 | 2 | "http": {
|
300 | 3 | "functions": {
|
301 | 4 | "Object.createServer()": "was called"
|
302 | 5 | }
|
303 | 6 | },
|
304 | 7 | "mock server": {
|
305 | 8 | "functions": {
|
306 | 9 | "Object.listen()": "was called",
|
307 | 10 | "Object.address()": "was called"
|
308 | 11 | }
|
309 | 12 | },
|
310 | 13 | "mock response": {
|
311 | 14 | "functions": {
|
312 | 15 | "Object.writeHead()": "was called",
|
313 | 16 | "Object.write()": "was NOT called", <--------------------
|
314 | 17 | "Object.end()": "was called"
|
315 | 18 | }
|
316 | 19 | }
|
317 | 20 | }
|
318 |
|
319 | ```
|
320 |
|
321 |
|
322 | ```coffee
|
323 | before ipso (done, should) ->
|
324 |
|
325 | tag
|
326 |
|
327 | Got: should.exist
|
328 | Not: should.not.exist
|
329 |
|
330 | .then done
|
331 |
|
332 | it 'has the vodka and the olive', ipso (martini, Got, Not) ->
|
333 |
|
334 | Got martini.vodka
|
335 | Got martini.olive
|
336 | Not martini.gin
|
337 |
|
338 | #
|
339 | # * there is great value in using **only** local scope in test... (!)
|
340 | #
|
341 |
|
342 | ```
|
343 |
|
344 |
|
345 | It supports promises.
|
346 |
|
347 | ```coffee
|
348 |
|
349 | it 'fails the test on the first rejection in the chain', ipso (facto, Module) ->
|
350 |
|
351 | Module.functionThatReturnsAPromise()
|
352 |
|
353 | .then -> Module.functionThatReturnsAPromise()
|
354 | .then -> Module.functionThatReturnsAPromise()
|
355 | .then -> Module.functionThatReturnsAPromise()
|
356 | .then -> facto()
|
357 |
|
358 | ```
|
359 |
|
360 | Ipso Facto
|
361 |
|
362 | ```coffee
|
363 |
|
364 | it 'does many things to come', ipso (facto, ...) ->
|
365 |
|
366 | facto[MetaThings]()
|
367 |
|
368 | #
|
369 | # facto() calls mocha's done() in the background
|
370 | #
|
371 |
|
372 | ```
|
373 |
|
374 | What MetaThings?
|
375 |
|
376 | * well, ... (( the brief brainstorm suggested a Planet-sized Plethora of Particularly Peachy Possibilities Perch Patiently Poised Pending a Plunge into **That** rabbit hole.
|
377 |
|
378 |
|
379 | And who is Unthahorsten?
|
380 |
|
381 | * And why was he doing the equivalent of standing in the equivalent of a laboratory.
|
382 |
|
383 |
|
384 | cli
|
385 | ---
|
386 |
|
387 | * There is a cli.
|
388 | * It is tailored fairly tightly to my size and shape of process.
|
389 |
|
390 | **However**, there are options:
|
391 |
|
392 | ```
|
393 |
|
394 | Usage: ipso [options]
|
395 |
|
396 | Options:
|
397 |
|
398 | -h, --help output usage information
|
399 | -V, --version output the version number
|
400 | -w, --no-watch Dont watch spec and src dirs.
|
401 | -n, --no-env Dont load .env.test
|
402 | -m, --mocha Use mocha.
|
403 | -e, --alt-env [name] Loads .env.name
|
404 | --spec [dir] Specify alternate spec dir.
|
405 | --src [dir] Specify alternate src dir.
|
406 | --lib [dir] Specify alternate compile target.
|
407 |
|
408 | ```
|
409 |
|
410 | ### Highlight
|
411 |
|
412 | It can quickly start up a node-inspector session on a v8 debugger socket. It may at some point seamlessly attach to the running tests, with `Module.does(...)` specifying breakpoints. (that would be very! very! nice...)
|
413 |
|
414 | ```
|
415 | $ ipso --mocha -e name
|
416 | ipso: warning: .env.name is PRODUCTION
|
417 | ipso: loaded .env.name
|
418 | ipso: watching directory: ./spec
|
419 | ipso: watching directory: ./src
|
420 | >
|
421 | >
|
422 | > inspect 3001 5860 lib/examples/basic.js
|
423 | >
|
424 | > debugger listening on port 5860 <----------------------------
|
425 | > Node Inspector v0.5.0
|
426 | > info: socket.io started
|
427 | > Visit http://127.0.0.1:3001/debug?port=5860 to start debugging.
|
428 | > =====================================
|
429 | ```
|
430 |
|
431 | ### Specific!ness
|
432 |
|
433 | It watches for ./src and ./spec changes and runs the changed.
|
434 |
|
435 | `ipso --mocha --src [dir] --spec [dir] --lib [dir]`
|
436 |
|
437 | * ./src changes will be compiled into ./lib/...
|
438 | * the corresponding test will then be run from ./spec/...
|
439 | * the followingly illustrated "path echo" **is assumed to ALWAYS be the case**
|
440 |
|
441 | ```
|
442 | src/same/dirname/source_name.coffee
|
443 | spec/same/dirname/source_name_spec.coffee
|
444 | ```
|