Kava is built upon TaskGroup which offers all the same benefits as the other testing libraries, but with these superior benefits:
It accurately associates tests with suites. This is something which other test utilities merely guess, which leads them to false positives, especially with asynchronous tests.
It doesn't need a special runtime setup. Your tests can be run just like any other script you write. Allowing you to use your existing knowledge to write and debug your tests.
In general, it can be stated that kava produces a state of calmness, relaxation and well-being without diminishing cognitive performance.
To make use of Kava for your testing, you must first install it:
npm install --save-dev kava
Usage
Using Kava is as simple as requiring it.
Let's say we are writing a package called myfirstpackage, generally we would write a test.js file which makes use of Kava like so:
test.js
constkava=require('kava')kava.suite('myfirstpackage',function (suite, test) {suite('a group of tests',function (suite, test) {test('an individual test',function () {/* the testing logic for this test would go here */ }) })})
To run our tests with Kava, we simply run our tests:
node ./test.js
Which would produce the following:
myfirstpackage
myfirstpackage ➞ a group of tests
myfirstpackage ➞ a group of tests ➞ an individual test
myfirstpackage ➞ a group of tests ➞ an individual test ✔
myfirstpackage ➞ a group of tests ✔
myfirstpackage ✔
1/1 tests ran successfully, everything passed
Architecture
Kava under the hood is powered by the robust TaskGroup flow control library.
Tests in Kava are extensions of the TaskGroup's Task class. Suites in Kava (a collection of tests) are extensions of TaskGroup's TaskGroup class.
This makes the Kava codebase incredibly simple yet incredibly powerful thanks to the existing robustness of TaskGroup, which already offers flow control, error reporting, and optional completion callbacks. All Kava needs to add, is the syntactic sugar and reporting that we come to expect from a modern testing framework.
Tests
Tests can be written like so:
test('my synchronous test name',function () {console.log('running our test')})
Which will output:
my synchronous test name
running our test
my synchronous test name ✔
1/1 tests ran successfully, everything passed
Tests can also become asynchronous by accepting a completion callback like so:
my asynchronous test name
starting test...
...finishing test
my asynchronous test name ✔
1/1 tests ran successfully, everything passed
Test Examples
Here is an example of a synchronous read file test:
constfile= __filenametest('read the current file and assert it contains the word test',function () {// if this throws for whatever reason, the error will be caught by Kava, and the test marked as failedif ( require('fs').readFileSync(file).indexOf('test') ===-1 ) {// errors can also be returned rather than thrownreturnnewError(`could not find the word test within the file ${file}`) }})
Here is an example of an asynchronous read file test:
constfile= __filenametest('reading the current file is possible',function (done) {console.log('commencing read of', file)require('fs').readFile(file,function (err, data) {// if there was an error, let your completion callback knowif ( err ) returndone(err)// if there wasn't an error, let us knowconsole.log('file read successfully')// call our completion callback to signify this test is now completedone() })})
Suites
A suite is a collection of tests. They can be written like so:
suite('main suite',function (suite, test) {test('async test within main suite',function (done) {console.log(' starting [async test within main suite]...')setTimeout(function () {console.log(' ...finishing [async test within main suite]')done() },1000) })test('sync test within main suite',function () {console.log(' running [sync test within main suite]') })// create a child suite within the main suitesuite('child suite',function (suite, test) {test('async test within child suite',function (done) {console.log(' starting [async test within child suite]...')setTimeout(function () {console.log(' ...finishing [async test within child suite]')done() },1000) })test('sync test within child suite',function () {console.log(' running [sync test within child suite]') }) })})
The receiving of the suite and test arguments, and their usage within, creates proper nesting structures, such that children are actually children and are treated as such, in regards to reporting and flow execution, as seen by the output of the above:
main suite
main suite ➞ async test within main suite
starting [async test within main suite]......
...finishing [async test within main suite]
main suite ➞ async test within main suite ✔
main suite ➞ sync test within main suite
running [sync test within main suite]
main suite ➞ sync test within main suite ✔
main suite ➞ child suite
main suite ➞ child suite ➞ test within child suite
starting [async test within child suite]......
...finishing [async test within child suite]
main suite ➞ child suite ➞ async test within child suite ✔
main suite ➞ child suite ➞ sync test within main suite
running [sync test within child suite]
main suite ➞ child suite ➞ sync test within child suite ✔
main suite ➞ child suite ✔
main suite ✔
4/4 tests ran successfully, everything passed
As you would notice from the above, by default Suites execute their items serially, that is one after the other, waiting for the current item to complete before starting the next. This is a sensible default, and is inherited from Suites inheritance of TaskGroup.
Suite Concurrency
As Suites do inherit from TaskGroup, and as TaskGroup allows us to configure the concurrency via the concurrency option, we could have our suite execute in parallel by doing:
suite('main suite',function (suite, test) {// make this suite execute in parallel with unlimited concurrencythis.setConfig({concurrency:0})// continue just like before// ...})
Which will alter the output to be the following:
main suite
main suite ➞ async test within main suite
starting [async test within main suite]......
main suite ➞ sync test within main suite
running [sync test within main suite]
main suite ➞ sync test within main suite ✔
main suite ➞ child suite
main suite ➞ child suite ➞ test within child suite
starting [async test within child suite]......
...finishing [async test within child suite]
main suite ➞ child suite ➞ async test within child suite ✔
main suite ➞ child suite ➞ sync test within main suite
running [sync test within child suite]
main suite ➞ child suite ➞ sync test within child suite ✔
main suite ➞ child suite ✔
...finishing [async test within main suite]
main suite ➞ async test within main suite ✔
main suite ✔
4/4 tests ran successfully, everything passed
Suite Initialisation
Suites can also create tasks asynchronously by the use of a completion callback for the suite initialiser.
suite('main suite',function (suite, test, done) {// add some tests immediately just like beforetest('this test is added to the suite immediately, just like in the previous example',function () {//code for your test })// add some tests after a 1 second delaysetTimeout(function () {test('this test is added to the suite after a 1 second delay',function () {// code for your test })suite('this suite is added to the suite after a 1 second delay',function (suite, test) {test('some test',function () {// code for your test }) })// call our suites completion callback to signify the suite is now ready to rundone() },1000)})
This is because under the hood, the suite initialiser is just a TaskGroup Task, that Kava configures with the suite and test arguments, and the TaskGroup pops on the optional completion callback and executes it accordingly.
Suite Example
Here is an example making use of all that we learned above:
// PrepareconstfsUtil=require('fs')constpathUtil=require('path')constdir= __dirname// Create our suite, that will read the directory, and add tests for filesuite(`all files within ${dir} contain the word test`,function (suite, test, complete) {// Make the file tests occur in parallel to save timethis.setConfig({concurrency:0})// Add the file tests by reading the directoryfs.readdir(dir,function (err, _files) {// Abort the test if there was an errorif ( err ) returncomplete(err)// Add the tests for each filefiles.forEach(function (file) {constpath=pathUtil.join(dir, file)// Add the test for reading and checking the filetest(`checking ${file}`,function (complete) {fs.readFile(path,function (err, data) {if ( err ) returncomplete(err)if ( data.toString().indexOf('test') ===-1 ) {returncomplete(newError('could not find the word test')) }returncomplete() }) }) })// Directory has been read, tests have been added, signal our initialisation is complete// so that the items within our suite can be runreturncomplete() })})
Hooks
Kava gives us support for before and after hooks for our tests and suites, such that we can perform synchronous and asynchronous actions before or after a specific test or suite, or all tests or suites within a particular suite.
To add before and after hooks to a specific Suite or Test, we would pass our listeners into the before and after options like so:
functionbefore (complete) {console.log('this is running before the test:',this.name)setTimeout(function () {console.log('and has a completion callback making it asynchronous')complete() },1000)}functionafter () {console.log('this is running after the test:',this.name)}kava.test('our test with hooks', {before, after},function (suite, test) {console.log('our test is running')})
To add before and after hooks for every item within a suite, we can use event listeners like so:
kava.suite('our suite',function (suite, test) {this.on('test.before',function (test, complete) {console.log('this is running before the test:',test.name)setTimeout(function () {console.log('and has a completion callback making it asynchronous')complete() },1000) })this.on('test.after',function (test, complete) {console.log('this is running after the test:',test.name) })test('first test',function (complete) {setTimeout(function () {console.log('this is the output from the first test')complete() },1000) })test('second test',function () {console.log('this is the output from the second test') })})
Reporters
Kava also supports different reporters. Reporters listen to the status of suites and tests and output their results to you.
The default reporter is the Console reporter. However, there is another builtin reporter called the List reporter which outputs only the results of tests, rather than their commencement.
You can select the reporter you wish to use via the KAVA_REPORTER environment variable like so:
env KAVA_REPORTER=list node ./test.js
Summary
This page covers everything you need to utilise Kava for your testing purposes.