UNPKG

19.4 kBMarkdownView Raw
1# zora
2
3Fast javascript test runner for **nodejs** and **browsers**
4
5[![CircleCI](https://badgen.net/circleci/github/lorenzofox3/zora)](https://circleci.com/gh/lorenzofox3/zora)
6[![npm](https://badgen.net/npm/v/zora)](https://www.npmjs.com/package/zora)
7[![install size](https://badgen.net/packagephobia/install/zora)](https://packagephobia.now.sh/result?p=zora)
8
9## installation
10
11``npm install --save-dev zora@2``
12
13Note that the version 3 of zora targets modern Javascript engines. Behind the scene it uses *Asynchronous iterators* and *for await* statement. Both
14are supported by Node (>= 10 or >= 8 with flag) and all the major browsers. If you wish to use the v2 you can find its code and documentation on the [v2 branch](https://github.com/lorenzofox3/zora/tree/v2).
15
16## (Un)Opinions and Design
17
18These are the following rules and ideas I have followed while developing zora. Whether they are right or not is an entire different topic ! :D
19Note I have decided to develop zora specially because I was not able to find a tool which complies entirely with these ideas.
20
21### Tests are regular Javascript programs.
22
23You don't need a specific test runner, a specific platform or any build step to run your `zora` tests. They are only regular valid EcmaScript 2018 programs.
24If you have the following test.
25```Javascript
26import {test} from 'path/to/zora';
27
28test('should result to the answer', t => {
29 const answer = 42
30 t.equal(answer, 42, 'answer should be 42');
31});
32```
33
34You can run your test with
351. Node: ``node ./myTestFile.js``
362. In the browser ``<script type="module" src="./myTestFile.js></script>`` identically
37
38Moreover zora does not use specific platform API which should make it transparent to most of your tools such module bundlers or transpilers.
39
40In few words:
41> Zora is Ecmascript, no less, no more.
42
43### Tests are fast
44
45Tests are part of our daily routine as software developers. Performance is part of the user experience and there is no reason you should wait seconds for your tests to run.
46Zora is by far the **fastest** Javascript test runner in the ecosystem.
47
48#### Benchmark
49
50This repository includes a benchmark which consists on running N test files, with M tests in each and where one test lasts T milliseconds.
51About 5% of tests should fail.
52
531. profile library: N = 5, M = 8, T = 25ms
542. profile web app: N = 10, M = 8, T = 40ms
553. profile api: N =12, M = 10, T = 100ms
56
57Each framework runs with its default settings.
58
59Here are the result of different test frameworks on my developer machine (MacBook Pro, 2.7GH i5) with node 10.11.0 :
60
61| | zora@3.0.0 | tape@4.9.2 | Jest@22.2.2 | AvA@1.0.0 | Mocha@5.2.0|
62|--------|:------------:|:-----------:|:-------------:|:------------:|------------:|
63|Library | 123ms | 1251ms | 2397ms | 2100ms | 1406ms |
64|Web app | 146ms | 3572ms | 5048ms | 2930ms | 3765ms |
65|API | 226ms | 12563ms | 14928ms | 3431ms | 12742ms |
66
67Of course as any benchmark, it may not cover your use case and you should probably run your own tests before you draw any conclusion.
68
69### Focus on tests only
70
71zora does one thing but hopefully does it well: **test**.
72
73In my opinions:
741. Pretty reporting (I have not said *efficient reporting*) should be handled by a specific tool.
752. Transpilation and other code transformation should be handled by a specific tool.
763. File watching and caching should be handled by a specific tool.
774. File serving should be handled by a specific tool.
785. Coffee should me made by a specific tool.
79
80As a result zora is way more smaller to install according to [packagephobia](https://packagephobia.now.sh) than all the others test frameworks
81
82| | zora@3.0.0 | tape@4.9.2 | Jest@22.2.2 | AvA@1.0.0 | Mocha@5.2.0|
83|--------|:------------:|:-----------:|:-------------:|:------------:|------------:|
84|Install size | [153kb](https://packagephobia.now.sh/result?p=zora) | [895kb](https://packagephobia.now.sh/result?p=tape) | [34.6mb](https://packagephobia.now.sh/result?p=jest) | [14.2mb](https://packagephobia.now.sh/result?p=ava) | [1.86mb](https://packagephobia.now.sh/result?p=mocha)|
85
86### Reporter is handled with other process (TAP aware)
87
88When you run a test you usually want to know whether there is any failure, where and why in order to debug and solve the issue as fast as possible.
89Whether you want it to be printed in red, yellow etc is a matter of preference.
90
91For this reason, zora output [TAP](http://testanything.org/) (Test Anything Protocol) by default. This protocol is "machine friendly" and widely used: [there are plenty of tools](https://github.com/sindresorhus/awesome-tap) to parse and deal with it the way **you** want.
92
93## Usage
94
95### Basics
96
97You can use the top level assertion methods
98
99```Javascript
100import {equal, ok, isNot} from 'zora';
101
102ok(true,'true is truthy');
103
104equal('bar','bar', 'that both string are equivalent');
105
106isNot({},{},'those are not the same reference');
107
108//etc
109```
110
111If you run the previous program, test report will start on its own by default with the following console output:
112
113```TAP
114TAP version 13
115ok 1 - true is truthy
116ok 2 - that both string are equivalent
117ok 3 - those are not the same reference
1181..3
119
120# ok
121# success: 3
122# skipped: 0
123# failure: 0
124```
125
126However one will usually want to group assertions within a sub test: the ``test`` method can be used.
127
128```Javascript
129import {test} from 'zora';
130
131test('some grouped assertions', t => {
132 t.ok(true, 'true is truthy');
133 t.equal('bar', 'bar', 'that both string are equivalent');
134 t.isNot({}, {}, 'those are not the same reference');
135});
136```
137
138with the following result
139
140```TAP
141TAP version 13
142# some grouped assertions
143ok 1 - true is truthy
144ok 2 - that both string are equivalent
145ok 3 - those are not the same reference
1461..3
147
148# ok
149# success: 3
150# skipped: 0
151# failure: 0
152```
153
154You can also group tests within a parent test:
155
156```Javascript
157import {test} from 'zora';
158
159test('some grouped assertions', t => {
160 t.ok(true, 'true is truthy');
161
162 t.test('a group inside another one', t=>{
163 t.equal('bar', 'bar', 'that both string are equivalent');
164 t.isNot({}, {}, 'those are not the same reference');
165 });
166});
167```
168
169```TAP
170TAP version 13
171# some grouped assertions
172ok 1 - true is truthy
173# a group inside another one
174ok 2 - that both string are equivalent
175ok 3 - those are not the same reference
1761..3
177
178# ok
179# success: 3
180# skipped: 0
181# failure: 0
182```
183
184### Asynchronous tests and control flow
185
186Asynchronous tests are simply handled with async function:
187
188```Javascript
189test('with getUsers an asynchronous function returning a Promise',async t => {
190 const users = await getUsers();
191 t.eq(users.length, 2,'we should have 2 users');
192});
193```
194
195Notice that each test runs in its own micro task in parallel (for performance). It implies your tests should not depend on each other.
196It is often a good practice!
197However, you'll be able to group your tests if you wish to conserve some state between them or wait one to finish before you start another one (ideal with tests running against real database).
198
199The sequence is simply controlled by AsyncFunction (and await keyboard), the ``test`` function return the result of its spec function argument, so you can control whether you want a specific test to complete before moving on
200
201```Javascript
202let state = 0;
203
204test('test 1', t => {
205 t.ok(true);
206 state++;
207});
208
209test('test 2', t => {
210 //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
211 t.equal(state, 1);
212});
213
214//Same thing here even in nested tests
215test('grouped', t => {
216 let state = 0;
217
218 t.test('test 1', t => {
219 t.ok(true);
220 state++;
221 });
222
223 t.test('test 2', t => {
224 //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
225 t.equal(state, 1);
226 });
227});
228
229//And
230test('grouped', t=>{
231 let state = 0;
232
233 t.test('test 1', async t=>{
234 t.ok(true);
235 await wait(100);
236 state++;
237 });
238
239 test('test 2', t=>{
240 t.equal(state, 0, 'see the old state value as it will have started to run before test 1 is done');
241 });
242});
243
244//But
245test('grouped', async t => {
246 let state = 0;
247
248 //specifically wait the end of this test before continuing !
249 await t.test('test 1', async t => {
250 t.ok(true);
251 await wait(100);
252 state++;
253 });
254
255 test('test 2', t => {
256 t.equal(state, 1, 'see the updated value!');
257 });
258});
259```
260
261### Changing TAP format
262
263TAP protocol is loosely defined in the sense that diagnostic is quite a free space and there is no well defined format to explicit a sub tests.
264In Javascript community most of the TAP parsers and tools were designed for [tape](https://github.com/substack/tape) which implies a TAP comment for a sub test header and every assertion is on the same level.
265In the same way these aforementioned tools expect diagnostics with a ``expected``, ``actual``, etc properties
266It is the one we have used in our previous examples.
267
268If you run the following program
269```Javascript
270import {test} from 'zora';
271
272test('tester 1', t => {
273
274 t.ok(true, 'assert1');
275
276 t.test('some nested tester', t => {
277 t.ok(true, 'nested 1');
278 t.ok(true, 'nested 2');
279 });
280
281 t.test('some nested tester bis', t => {
282 t.ok(true, 'nested 1');
283
284 t.test('deeply nested', t => {
285 t.ok(true, 'deeply nested really');
286 t.ok(true, 'deeply nested again');
287 });
288
289 t.notOk(true, 'nested 2'); // This one will fail
290 });
291
292 t.ok(true, 'assert2');
293});
294
295test('tester 2', t => {
296 t.ok(true, 'assert3');
297
298 t.test('nested in two', t => {
299 t.ok(true, 'still happy');
300 });
301
302 t.ok(true, 'assert4');
303});
304```
305
306You will see in the console
307```TAP
308TAP version 13
309# tester 1
310ok 1 - assert1
311# some nested tester
312ok 2 - nested 1
313ok 3 - nested 2
314# some nested tester bis
315ok 4 - nested 1
316# deeply nested
317ok 5 - deeply nested really
318ok 6 - deeply nested again
319not ok 7 - nested 2
320 ---
321 pass: false
322 actual: true
323 expected: "falsy value"
324 description: "nested 2"
325 operator: "notOk"
326 at: " t.test.t (/Volumes/Data/code/zora/test/samples/cases/nested.js:20:11)"
327 id: 7
328 ...
329ok 8 - assert2
330# tester 2
331ok 9 - assert3
332# nested in two
333ok 10 - still happy
334ok 11 - assert4
3351..11
336
337# not ok
338# success: 10
339# skipped: 0
340# failure: 1
341```
342
343Another common structure is the one used by [node-tap](http://node-tap.org/). The structure can be parsed with common tap parser (such as [tap-parser](https://github.com/tapjs/tap-parser)) And will be parsed as well by tap parser which
344do not understand the indentation. However to take full advantage of the structure you should probably use a formatter (such [tap-mocha-reporter](https://www.npmjs.com/package/tap-mocha-reporter)) aware of this specific structure to get the whole benefit
345of the format.
346
347![tap output in a BDD format](./media/bsd.png)
348
349If you call the ``indent`` method on the ``test`` function anywhere in your test the output stream will indent sub tests and use other properties for diagnostic:
350
351```Javascript
352const {test} = require('zora.js');
353
354test.indent(); // INDENT
355
356test('tester 1', t => {
357
358 t.ok(true, 'assert1');
359
360 t.test('some nested tester', t => {
361 t.ok(true, 'nested 1');
362 t.ok(true, 'nested 2');
363 });
364
365 t.test('some nested tester bis', t => {
366 t.ok(true, 'nested 1');
367
368 t.test('deeply nested', t => {
369 t.ok(true, 'deeply nested really');
370 t.ok(true, 'deeply nested again');
371 });
372
373 t.notOk(true, 'nested 2'); // This one will fail
374 });
375
376 t.ok(true, 'assert2');
377});
378
379test('tester 2', t => {
380 t.ok(true, 'assert3');
381
382 t.test('nested in two', t => {
383 t.ok(true, 'still happy');
384 });
385
386 t.ok(true, 'assert4');
387});
388```
389
390will give you the following result
391```TAP
392TAP version 13
393# Subtest: tester 1
394 ok 1 - assert1
395 # Subtest: some nested tester
396 ok 1 - nested 1
397 ok 2 - nested 2
398 1..2
399 ok 2 - some nested tester # 1ms
400 # Subtest: some nested tester bis
401 ok 1 - nested 1
402 # Subtest: deeply nested
403 ok 1 - deeply nested really
404 ok 2 - deeply nested again
405 1..2
406 ok 2 - deeply nested # 1ms
407 not ok 3 - nested 2
408 ---
409 wanted: "falsy value"
410 found: true
411 at: " t.test.t (/Volumes/Data/code/zora/test/samples/cases/nested.js:22:11)"
412 operator: "notOk"
413 ...
414 1..3
415 not ok 3 - some nested tester bis # 1ms
416 ok 4 - assert2
417 1..4
418not ok 1 - tester 1 # 1ms
419# Subtest: tester 2
420 ok 1 - assert3
421 # Subtest: nested in two
422 ok 1 - still happy
423 1..1
424 ok 2 - nested in two # 0ms
425 ok 3 - assert4
426 1..3
427ok 2 - tester 2 # 0ms
4281..2
429
430# not ok
431# success: 10
432# skipped: 0
433# failure: 1
434```
435
436### Skip a test
437
438You can decide to skip some tests if you wish not to run them, in that case they will be considered as _passing_. However the assertion summary at the end will tell you that some tests have been skipped
439and each skipped test will have a tap skip directive.
440
441```Javascript
442import {ok, skip, test} from 'zora';
443
444ok(true, 'hey hey');
445ok(true, 'hey hey bis');
446
447test('hello world', t => {
448 t.ok(true);
449 t.skip('blah', t => {
450 t.ok(false);
451 });
452 t.skip('for some reason');
453});
454
455skip('failing text', t => {
456 t.ok(false);
457});
458```
459
460```TAP
461TAP version 13
462ok 1 - hey hey
463ok 2 - hey hey bis
464# hello world
465ok 3 - should be truthy
466# blah
467ok 4 - blah # SKIP
468# for some reason
469ok 5 - for some reason # SKIP
470# failing text
471ok 6 - failing text # SKIP
4721..6
473
474# ok
475# success: 3
476# skipped: 3
477# failure: 0
478```
479
480### Assertion API
481
482- equal<T>(actual: T, expected: T, message?: string) verify if two values/instances are equivalent. It is often described as *deepEqual* in assertion libraries.
483aliases: eq, equals, deepEqual
484- notEqual<T>(actual: T, expected: T, message?: string) opposite of equal.
485aliases: notEquals, notEq, notDeepEqual
486- is<T>(actual: T, expected: T, message ?: string) verify whether two instances are the same (basically it is Object.is)
487aliases: same
488- isNot<T>(actual: T, expected: T, message ?: string)
489aliases: notSame
490- ok<T>(actual: T, message?: string) verify whether a value is truthy
491aliases: truthy
492- notOk<T>(actual: T, message?:string) verify whether a value is falsy
493aliases: falsy
494- fail(message?:string) an always failing test, usually when you want a branch of code not to be traversed
495- throws(fn: Function, expected?: string | RegExp | Function, description ?: string) expect an error to be thrown, you check the expected error by Regexp, Constructor or name
496- doesNotThrow(fn: Function, expected?: string | RegExp | Function, description ?: string) expect an error not to be thrown, you check the expected error by Regexp, Constructor or name
497
498### Create manually a test harness
499
500You can discard the default test harness and create your own. This has various effect:
501- the reporting won't start automatically, you will have to trigger it yourself but it also lets you know when the reporting is over
502- you can pass a custom reporter. Zora produces a stream of messages which are then transformed into a TAP stream. If you create the test harness yourself
503you can directly pass your custom reporter to transform the raw messages stream.
504
505```Javascript
506const {createHarness, mochaTapLike} = require('zora');
507
508const harness = createHarness();
509const {test} = harness;
510
511test('a first sub test', t => {
512 t.ok(true);
513
514 t.test('inside', t => {
515 t.ok(true);
516 });
517});
518
519test('a first sub test', t => {
520 t.ok(true);
521
522 t.test('inside', t => {
523 t.ok(false, 'oh no!');
524 });
525});
526
527harness
528 .report(mochaTapLike) // we have passed the mochaTapLike (with indention but here you can pass whatever you want
529 .then(() => {
530 // reporting is over: we can release some pending resources
531 console.log('DONE !');
532 // or in this case, our test program is for node so we want to set the exit code ourselves in case of failing test.
533 const exitCode = harness.pass === true ? 0 : 1;
534 process.exit(exitCode);
535 });
536```
537
538In practice you won't use this method unless you have specific requirements or want to build your own test runner on top of zora.
539
540## In the browser
541
542Zora itself does not depend on native Nodejs modules (such file system, processes, etc) so the code you will get is regular EcmaScript.
543
544### drop in file
545You can simply drop the dist file in the browser and write your script below (or load it).
546You can for example play with this [codepen](https://codepen.io/lorenzofox3/pen/YBWJrJ)
547
548```Html
549<!-- some content -->
550<body>
551<script type="module">
552
553import test from 'path/to/zora';
554
555test('some test', (assert) => {
556 assert.ok(true, 'hey there');
557})
558
559test('some failing test', (assert) => {
560 assert.fail('it failed');
561})
562</script>
563</body>
564<!-- some content -->
565```
566
567### As part of CI (example with rollup)
568
569I will use [rollup](http://rollupjs.org/) for this example, but you should not have any problem with [webpack](https://webpack.github.io/) or [browserify](http://browserify.org/). The idea is simply to create a test file your testing browsers will be able to run.
570
571assuming you have your entry point as follow :
572```Javascript
573//./test/index.js
574import test1 from './test1.js'; // some tests here
575import test2 from './test2.js'; // some more tests there
576import test3 from './test3.js'; // another test plan
577```
578
579where for example ./test/test1.js is
580```Javascript
581import test from 'zora';
582
583test('mytest', (assertions) => {
584 assertions.ok(true);
585})
586
587test('mytest', (assertions) => {
588 assertions.ok(true);
589});
590```
591you can then bundle your test as single program.
592
593```Javascript
594const node = require('rollup-plugin-node-resolve');
595const commonjs = require('rollup-plugin-commonjs');
596module.exports = {
597 input: './test/index.js',
598 output: [{
599 name: 'test',
600 format: 'iife',
601 sourcemap: 'inline' // ideal to debug
602 }],
603 plugins: [node(), commonjs()], //you can add babel plugin if you need transpilation
604};
605```
606
607You can now drop the result into a debug file
608``rollup -c path/to/conf > debug.js``
609
610And read with your browser (from an html document for example).
611
612![tap output in the browser console](./media/console-sc.png)
613
614Even better, you can use tap reporter browser friendly such [tape-run](https://www.npmjs.com/package/tape-run) so you'll have a proper exit code depending on the result of your tests.
615
616so all together, in your package.json you can have something like that
617```Javascript
618{
619// ...
620 "scripts": {
621 "test:ci": "rollup -c path/to/conf | tape-run"
622 }
623// ...
624}
625```
626
627## On exit codes
628
629Whether you have failing tests or not, unless you have an unexpected error, the process will return an exit code 0: zora considers its duty is to run the program to its end whether there is failing test or no.
630Often CI platforms require an exit code of 1 to mark a build as failed. That is not an issue, there are plenty of TAP reporters which when parsing a TAP stream will exit the process with code 1 if they encounter a failing test.
631Hence you'll need to pipe zora output into one of those reporters to avoid false positive on your CI platform.
632
633For example, one of package.json script can be
634``"test:ci": npm test | tap-set-exit``