UNPKG

11.4 kBMarkdownView Raw
1# zora
2
3Fast javascript test runner for **nodejs** and **browsers**
4
5[![CircleCI](https://circleci.com/gh/lorenzofox3/zora.svg?style=svg)](https://circleci.com/gh/lorenzofox3/zora)
6
7## installation
8``npm install --save-dev zora``
9
10## (Un)Opinions and Design
11
12These are the following rules and ideas I have followed while developing zora. Whether they are right or not is an entire different topic ! :D
13Note I have decided to develop zora specially because I was not able to find a tool which complies entirely with these ideas.
14
15### Tests are regular Javascript programs.
16
17You 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 2017 programs.
18If you have the following test.
19```Javascript
20import test from 'path/to/zora';
21
22test('should result to the answer', t=>{
23 const answer = 42
24 t.equal(answer, 42, 'answer should be 42');
25});
26```
27
28You can run your test with
291. Node: ``node ./myTestFile.js``
302. In the browser ``<script type="module" src="./myTestFile.js></script>`` identically
31
32Moreover zora does not use specific platform API which should make it transparent to most of your tools such module bundlers or transpilers.
33
34In few words:
35> Zora is only Javascript, no less, no more.
36
37### Tests are fast
38
39Tests 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.
40Zora is by far the **fastest** Javascript test runner in the ecosystem.
41
42### Benchmark
43
44This repository includes a benchmark which consists on running N test files, with M tests in each and where one tests lasts T milliseconds.
45About 10% of tests should fail.
46
471. profile library: N = 5, M = 8, T = 25ms
482. profile web app: N = 10, M = 8, T = 40ms
493. profile api: N =12, M = 10, T = 100ms
50
51Each framework runs with its default settings
52
53Here are the result of different test frameworks on my developer machine with node 9.3.0 :
54
55| | zora@2.0.0 | tape@4.8.0 | Jest@22.2.2 | AvA@0.25.0 | Mocha@5.0.0|
56|--------|:------------:|:-----------:|:-------------:|:------------:|------------:|
57|Library | 118ms | 1239ms | 1872ms | 1703ms | 1351ms |
58|Web app | 142ms | 3597ms | 4356ms | 2441ms | 3757ms |
59|API | 203ms | 12637ms | 13385ms | 2966ms | 12751ms |
60
61Of course as any benchmark, it may not cover your use case and you should probably run your own tests before you draw any conclusion
62
63### Focus on tests only
64
65zora does one thing but hopefully does it well: **test**.
66
67In my opinions:
681. Pretty reporting (I have not said *efficient reporting*) should be handled by a specific tool.
692. Transipilation and other code transformation should be handled by a specific tool.
703. File watching and caching should be handled by a specific tool.
714. File serving should be handled by a specific tool.
725. Coffee should me made by a specific tool.
73
74As a result zora is only **1Kb** of code whereas it lets you use whatever better tool you want for any other specific task you may need within your workflow.
75
76### Control flow is managed the way you want with regular Javascript idioms
77
78Lately Ecmascript specification has improved a lot (and its various implementation) in term of asynchronous code.
79There is no reason you could not benefit from it while writing your tests.
80In zora, asynchronous code is as simple as async function, you do not need to manage a plan or to notify the test framework the end of a test
81
82```Javascript
83import test from 'zora';
84
85test('should handle async operation', async t => {
86 const user = await getUserAsync();
87 t.deepEqual(user,{name:'John doe'});
88});
89```
90
91### Reporter is handled with other process (TAP)
92
93When 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.
94Whether you want it to be printed in red, yellow etc is a matter of preference.
95
96For this reason, zora output [TAP](http://testanything.org/) (Test Anything Protocol) by default. This protocol is widely used and [there are plenty of tools](https://github.com/sindresorhus/awesome-tap) to parse and deal with it the way **you** want.
97
98You are not stuck into a specific reporting format, you'll be using a *standard* instead.
99
100## Usage
101
102Zora API is very straightforward: you have only **1** function and the assertion library is pretty standard and obvious.
103The tests will start by themselves with no extra effort.
104
105```Javascript
106import test from 'zora';
107
108test('some independent test', t=>{
109 t.equal(1+1,2,'one plus one should equal 2');
110});
111
112test('other independent test', t=>{
113 t.equal(1+2,3,'one plus two should equal 3');
114});
115```
116
117### Assertions API
118
119The assertion api you can use within your test is pretty simple and highly inspired by [tape](https://github.com/substack/tape)
120* ok
121* notOk
122* equal
123* notEqual
124* deepEqual
125* notDeepEqual
126* throws
127* doesNotThrow
128* fail
129
130### Control flow
131
132Notice that each test runs in its own micro task in parallel (for performance). It implies your tests should not depend on each other.
133It is often a good practice.
134However, 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).
135
136The sequence is simply controlled by AsyncFunction (and await keyboard)
137
138```Javascript
139
140let state = 0;
141
142test('test 1', t=>{
143 t.ok(true);
144 state++;
145});
146
147test('test 2', t=>{
148 //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
149 t.equal(state, 1);
150});
151
152//Same thing here even in nested tests
153t.test('grouped', t=>{
154 let state = 0;
155
156 t.test('test 1', t=>{
157 t.ok(true);
158 state++;
159 });
160
161 t.test('test 2', t=>{
162 //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
163 t.equal(state, 1);
164 });
165});
166
167//And
168t.test('grouped', t=>{
169 let state = 0;
170
171 t.test('test 1', async t=>{
172 t.ok(true);
173 await wait(100);
174 state++;
175 });
176
177 test('test 2', t=>{
178 t.equal(state, 0, 'see the old state value as it will have started to run before test 1 is done');
179 });
180});
181
182//But
183t.test('grouped', async t=>{
184 let state = 0;
185
186 //specifically wait the end of this test before continuing !
187 await t.test('test 1', async t=>{
188 t.ok(true);
189 await wait(100);
190 state++;
191 });
192
193 test('test 2', t=>{
194 t.equal(state, 1, 'see the updated value!');
195 });
196});
197```
198
199### BDD style
200
201zora also allows you to nest tests in each other in a [BDD style](https://en.wikipedia.org/wiki/Behavior-driven_development).
202You just need to call the `test` property of zora at root level.
203
204```
205import zora from 'zora';
206const describe = zora.test;
207
208// and write you tests
209describe('test 1', t => {
210
211 t.ok(true, 'assert1');
212
213 // inside simply call t.test for nested test
214 t.test('some nested test', t => {
215 t.ok(true, 'nested 1');
216 t.ok(true, 'nested 2');
217 });
218
219 t.test('some nested test bis', t => {
220 t.ok(true, 'nested 1');
221
222 t.test('deeply nested', t => {
223 t.ok(true, 'deeply nested really');
224 t.ok(true, 'deeply nested again');
225 });
226
227 t.ok(true, 'nested 2');
228 });
229
230 t.ok(true, 'assert2');
231});
232
233describe('test 2', t => {
234 t.ok(true, 'assert3');
235
236 t.test('nested in two', t => {
237 t.ok(true, 'still happy');
238 });
239
240 t.ok(true, 'assert4');
241});
242```
243
244The output is a valid tap output where sub plans are indented
245```
246TAP version 13
247 # Subtest: test 1
248 ok 1 - assert1
249 # Subtest: some nested test
250 ok 1 - nested 1
251 ok 2 - nested 2
252 1..2
253 # time=1ms
254 ok 2 - some nested test # time=1ms
255 # Subtest: some nested test bis
256 ok 1 - nested 1
257 # Subtest: deeply nested
258 ok 1 - deeply nested really
259 ok 2 - deeply nested again
260 1..2
261 # time=1ms
262 ok 2 - deeply nested # time=1ms
263 ok 3 - nested 2
264 1..3
265 # time=1ms
266 ok 3 - some nested test bis # time=1ms
267 ok 4 - assert2
268 1..4
269 # time=1ms
270ok 1 - test 1 # time=1ms
271 # Subtest: test 2
272 ok 1 - assert3
273 # Subtest: nested in two
274 ok 1 - still happy
275 1..1
276 # time=1ms
277 ok 2 - nested in two # time=1ms
278 ok 3 - assert4
279 1..3
280 # time=1ms
281ok 2 - test 2 # time=1ms
2821..2
283# ok
284```
285
286The structure can be parsed with common tap parser (such as [tap-parser]()) And will be parsed as well by tap parser which
287do 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
288of the format.
289
290![tap output in a BDD format](./media/bsd.png)
291
292### In the browser
293
294Zora itself does not depend on native nodejs modules (such file system, processes, etc) so the code you will get is regular EcmaScript.
295
296// todo add some more recipe (WIP) karma, dependencies, transpilations etc
297
298#### drop in file
299You can simply drop the dist file in the browser and write your script below (or load it).
300You can for example play with this [codepen](http://codepen.io/lorenzofox3/pen/zoejxv?editors=1112)
301
302```Html
303<!-- some content -->
304<body>
305<script type="module">
306
307import test from 'path/to/zora';
308
309test('some test', (assert) => {
310 assert.ok(true, 'hey there');
311})
312
313test('some failing test', (assert) => {
314 assert.fail('it failed');
315})
316</script>
317</body>
318<!-- some content -->
319```
320
321#### As part of CI (example with rollup)
322I 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.
323
324assuming you have your entry point as follow :
325```Javascript
326//./test/index.js
327import test1 from './test1.js'; // some tests here
328import test2 from './test2.js'; // some more tests there
329import test3 from './test3.js'; // another test plan
330```
331
332where for example ./test/test1.js is
333```Javascript
334import test from 'zora';
335
336test('mytest',(assertions) => {
337 assertions.ok(true);
338})
339
340test('mytest',(assertions) => {
341 assertions.ok(true);
342});
343```
344you can then bundle your test as single app.
345
346```Javascript
347const node = require('rollup-plugin-node-resolve');
348const commonjs = require('rollup-plugin-commonjs');
349module.exports = {
350 input: './test/index.js',
351 output:[{
352 name:'test',
353 format:'test',
354 sourcemap:'inline' // ideal to debug
355 }]
356 plugins: [node(), commonjs()], //you can add babel plugin if you need transpilation
357};
358```
359
360You can now drop the result into a debug file
361``rollup -c path/to/conf > debug.js``
362
363And read with your browser (from an html document for example).
364
365![tap output in the browser console](./media/console-sc.png)
366
367Even 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.
368
369so all together, in your package.json you can have something like that
370```Javascript
371{
372// ...
373 "scripts": {
374 "test": "rollup -c path/to/conf | tape-run"
375 }
376// ...
377}
378```
\No newline at end of file