Zoroaster is a modern JavaScript testing framework for Node.js. It introduces the concept of test contexts, which aim in helping to provide documentable and re-usable test infrastructure, across spec files in a single package, as well as across packages. It's a completely new and developer-friendly approach to writing tests, which greatly improves productivity, testing experience and the reliability on tests. In addition, it allows to write ES6 module syntax without Babel
.
For example, you can make use of https-context
to set-up a mock HTTP server with configurable responses and temp-context
to create and remove a temp directory ready for each test without having to worry about repetitively writing the same code across projects. The way tests are written allows to see IDE suggestions for every method and property available in a context. Because these packages are maintained as separate pieces of software, they are also tested which means that there are less chances of an error in test set-ups which could lead to false-positive results.
Are you fed up with mocha
or have you had enough chai
in your life? Is it not time to say good-bye to the old stereotype that the same software must be used every day? Say no more, Zoroaster is here to save our souls and bring a change.
yarn add -D zoroaster
npm i --save-dev zoroaster
- Table Of Contents
- Do You Want Testing Framework That...
- Quick Example
- Example
- Wiki
- Copyright & License
Zoroaster is the most modern Node.JS testing framework that addresses the full spectrum of developer's needs and innovates the way quality assurance is done on NPM packages.
Zoroaster does not have many dependencies and does not install Babel, yet it is able to run tests with import/export
statements. Having less dependencies in node_modules
means that any new dependencies needed for the project will be installed immediately without having to wait for linking to complete, and new projects can be started in seconds, resolving all testing framework's dependencies almost instantly. Furthermore, Zoroaster only loads 3 JavaScript files, that is itself (1000 lines of code optimised with Google Closure Compiler), the interface for a service context (34 lines) and the RegExp-based algorithm to transpile import/export
statements (when the -a
option is passed) which is also optimised. The tests will start and run in milliseconds.
Framework | Fetching | Linking | Disk | Node_Module Dirs | yarn.lock Lines | Downloads |
---|---|---|---|---|---|---|
Jest | 485 | 7407 | 59.75MB | 420 | 3614 | 3,713,921 |
Mocha | 115 | 2016 | 12.17MB | 103 | 785 | 2,523,262 |
Jasmine | 13 | 106 | 896KB | 13 | 88 | 1,040,918 |
Tape | 33 | 506 | 2.85MB | 33 | 228 | 411,483 |
Ava | 462 | 6605 | 34.34MB | 378 | 3281 | 122,355 |
Tap | 469 | 7905 | 94.12MB | 407 | 3375 | 101,879 |
Zoroaster | 4 | 31 | 448KB | 3 | 27 | 1096 |
Compared to the other frameworks, Jasmine test runner is the next most-lightweight one, followed by modest Tape and good old Mocha, however they don't support snapshots and don't work with ES6 modules out of the box. Although there is no browser version of zoroaster
at the moment, it is still possible to test the source code in the browser, for example by setting up a server that loads HTML pages with JS code, and using the headless Chrome to open those pages and get the result. There will be more support for browsers in the near future.
One of the disadvantages of conventional testing frameworks is that they force developers to put setup and tear-down logic directly in test suites, which prevents management of tests by files. Using local scopes to store test states, limited access to the JSDoc and breaking of the DRY (don't repeat yourself) principle makes testing inconvenient. Zoroaster is the first Context-Testing framework that approaches the problem from the completely different angle: there is no setups and teardowns on tests, there is a context, which can be initialised and destroyed. Contexts are kept in separate files and can be written as classes, which greatly improves developer experience by allowing to access contexts' documented APIs. The contexts can be tested on their own merit, i.e. there is an inherent possibility to "test the tests" to take the quality assurance to the next level. Unlike before and after eachs, test contexts can not only be shared by test suites in different files, but also be published and reused across projects, so that more time is left to actually writing the tests and rather than wasting it on repetitive setups.
π Read Context Testing Wiki >
A test is a function which passes inputs to a method and compares the output to the expected one. A single method can receive 100s different inputs, including edge cases. Normally, each input would be added as a new test, where the same logic is repeated to run the method. Zoroaster eliminates the need to repeat the same code over and over again, and allows to focus on only adding new inputs to the existing test base to cover larger search field of the method under test. The routine to create tests, or test constructor is called a mask and is written in JavaScript, whereas the test input/outputs and any additional parameters can be written in plain text, such as markdown
or any other language that is the most convenient for syntax highlighting. Mask testing in Zoroaster is highly configurable, and combined with contexts provides the quickest, easiest and most flexible way to complete test coverage. Testing streams is also possible with masks β it is only required to write the getTransform
or getReadable
methods, and the output will be automatically collected and compared against the expected mask result.
Creating CLI Node.JS applications is fun. Testing them is not so much, because there is always the need to create new child processes, manage their state, interact with them somehow and then assert on inputs and outputs. In addition to simple mask testing, Zoroaster has a special configuration object that can be passed to the mask called fork
, where it is possible to specify what module to fork, what options to pass to it and even what inputs should be entered into its stdin
when a value matching a RegExp
comes up. The arguments are taken from the mask result ("the plain file") input, and compared to stdout
and stderr
properties of the result. Now all the developers have to do is write their arguments, configure options, possibly use test context (such as temp-context
to create and delete temp directories and get their snapshots by the end of the test) and supply the expected output of the CLI program.
Although some people don't approve of snapshot testing, it is an extremely useful tool for regression testing. There is no difference between writing asserts within specs, specifying them in masks, or returning them in snapshots, except that in the first case it takes a lot of manual labour, in the second case they are more visible, and in the third case they only require a second to write, but provide the robust mechanism against unexpected changes in the future, and thus are a good regression testing strategy. There is no additional methods to be called to create a snapshot, tests only need to return a value. Moreover, snapshots' file extension can be specified so that they can be naturally inspected with syntax highlighting in the IDE (e.g., for markdown files), and custom serialisation algorithms can be implemented. If a test returns a stream, its data will also be collected prior to being tested against a snapshot.
These are the main features of Zoroaster β the testing framework made by professional Node.JS developers made for other professional Node.JS developers and quality assurance experts. Unfortunately, there is no coverage tool at the moment, but we hope to add one in the near future. Nonetheless, the test contexts, mask and fork testing and its small size and performance will make it the testing framework of choice for developers who are tired of old paradigms.
All Zoroaster tests are written in spec files and exported as tests suites which are objects.
For example, tests can be run against sync and async methods.
// example program source code
export const software = (type) => {
switch (type) {
case 'boolean':
return true
case 'string':
return 'string'
default:
return null
}
}
export const asyncSoftware = async (type) => {
await new Promise(r => setTimeout(r, 50))
return software(type)
}
The Context can be used as an alternative for in-test suite set-up and tear-down routines. Anything returned by tests will be compared against snapshots that will be created upon the first run of the test.
// Zoroaster test suite
import { ok, equal } from 'assert'
import { software, asyncSoftware } from './src'
class Context {
async _init() {
await new Promise(r => setTimeout(r, 100))
this._data = 'hello world;'
}
/** Returns the testing data */
get data() {
return this._data
}
}
/**
* @type {Object.<string, (c:Context)>}
*/
const TestSuite = {
context: Context,
'runs a test'() {
const res = software('boolean')
ok(res)
},
async 'runs an async test'() {
const res = await asyncSoftware('string')
equal(res, 'string')
},
async 'supports snapshots'({ data }) {
const res = await asyncSoftware('string')
return `${res} :: ${data}`
},
}
export default TestSuite
See how to write tests with Zoroaster in this section.
First, create a module which exports a TEST SUITE as an object in the test/spec
directory. Second, add TESTS as functions -- properties of the test suite. Implement the tests with basic assertion methods required from zoroaster/assert
, or use any other assertion library.
There are NO global functions and tests are just methods of test suites, which can be written using shorthand notation.
/* yarn example/Zoroaster */
import { ok, equal } from 'zoroaster/assert'
import Zoroaster from '../../src'
export default {
// standard test function
'has static variables'() {
ok(Zoroaster.AHURA_MAZDA)
ok(Zoroaster.ANGRA_MAINYU)
},
// recursive test suites
constructor: {
'creates a new Zoroaster instance with default name'() {
const zoroaster = new Zoroaster()
ok(zoroaster instanceof Zoroaster)
equal(zoroaster.name, 'Zarathustra')
},
'creates a new Zoroaster instance with a name'() {
const name = 'Ashu Zarathushtra'
const zoroaster = new Zoroaster(name)
equal(zoroaster.name, name)
const name2 = 'Zarathushtra Spitama'
const zoroaster2 = new Zoroaster(name2)
equal(zoroaster2.name, name2)
},
'has a balance of 0 when initialised'() {
const zoroaster = new Zoroaster()
equal(zoroaster.balance, 0)
},
},
}
export const checkParadise = {
'returns false when balance is less than 1000'() {
const zoroaster = new Zoroaster()
const actual = zoroaster.checkParadise()
ok(!actual)
},
}
Async functions are perfect to test with zoroaster testing framework
due to the concise async shorthand method notation.
{
async 'returns true when balance of 1000 met'() {
const zoroaster = new Zoroaster()
zoroaster.createWorld()
await Promise.all(
Array.from({ length: 900 }).map(async () => {
await zoroaster.side(Zoroaster.AHURA_MAZDA)
})
)
equal(zoroaster.balance, 1000)
const actual = zoroaster.checkParadise()
ok(actual)
},
}
All tests have to complete within the specified timeout.
To run the example test file, execute
yarn example/Zoroaster/
yarn run v1.5.1
$ node src/bin example/Zoroaster/test/spec --alamode
example/Zoroaster/test/spec
async-context
β returns correct country of origin
index
β has static variables
β decreases and increase balance asynchronously
constructor
β creates a new Zoroaster instance with default name
β creates a new Zoroaster instance with a name
β has a balance of 0 when initialised
methods
β creates a world
β destroys a world
β says a sentence
side
β increases balance when doing good deed
β decreases balance when doing bad deed
β throws an error when choosing an unknown side
checkParadise
β returns true when balance of 1000 met
β returns false when balance is less than 1000
object-context
β sets correct default name
innerMeta
β accesses parent context
β returns correct date of birth
π¦
Executed 17 tests.
β¨ Done in 0.92s.
GNU Affero General Public License v3.0
Β© Art Decoβ’ for ContextTesting 2020 |
---|