Low-overhead, parallel-first testkit harness with dependencies and a Mocha like UI updated for ESM.
import test from '@momsfriendlydevco/testa';
test('simple test', ()=> {
test.expect(1).to.be.ok;
test.expect(1).to.be.a('number');
});
// Setup tests using a chainable syntax
test('auth').id('auth').do(()=>
fetch('https://acme.com/auth', {
method: 'POST',
headers: {
Authentication: `Bearer ${config.authToken}`,
},
})
);
// Apply dependencies
test('ping')
.depends('auth') // Wait for the above auth test to complete before we run this
.slow('30s') // Mark test as running slow if it takes >= 30 seconds
.timeout('1m') // ... and time it out at 1 minute
.do(async ()=> {
/* ... */
})
// Split multi-step processes up into stages
test('fetch entities', async t => {
t.stage('fetch users');
let users = await (await fetch('https://acme.com/api/users')).json();
t.log('there are', users.length, 'in the system'); // Output supplemental information during a test
t.stage('fetch projects');
let projects = await (await fetch('https://acme.com/api/projects')).json();
if (projects.length == 0)
t.warn('no projects in system'); // Warn about things without exiting
t.dump(users); // Dump complex information flows to temporary files to be examined later
if (projects.length == 0 && users.length == 0)
return t.skip('need at least 1 user + 1 project to run test'); // Skip out and say why
});
// Usual shortcut syntax applies
test('foo')
.skip('TODO: Not yet ready') // Don't actually run this, and optionally say why
.do(()=> /* ... */)
test('foo').only(()=> /* ... */) // Mark that only this test should run
test.before(()=> /* ... */) // Setup a test to run before everything else
test.after(()=> /* ... */) // Setup a test to run after everything else
test.priority(50).do(()=> /* ... */) // Or use priority levels (higher runs first)Install into a project with npm i @momsfriendlydevco/testa or run as npx @momsfriendlydevco/testa.
Usage: testa [options] [files...]
Run testkits in parallel with dependencies
Options:
-l, --list List all queued tests and exit
-b, --bail Stop processing on the first error (implies
`--serial`)
-s, --serial Force run tests in serial (alias of `--limit
1`)
-p, --parallel <number> Set number of tests to run in parallel
(default: 5)
-g, --grep <expression> Add a grep expression filter for tests titles
+ IDs (can be specified multiple times)
(default: [])
-G, --invert-grep <expression> Add an inverted grep expression filter for
tests titles + IDs (can be specified multiple
times) (default: [])
-f, --fgrep <expression> Add a raw string expression filter for tests
titles + IDs (can be specified multiple
times) (default: [])
-F, --invert-fgrep <expression> Add an inverted raw string expression filter
for tests titles + IDs (can be specified
multiple times) (default: [])
--slow [timestring] Set the amount of time before a test is
considered slow to resolve. Can be any valid
timestring (default: "75ms")
--timeout [timestring] Set the amount of time before a test times
out. Can be any valid timestring (default:
"2s")
--ui [ui] Set the UI environment to use (default:
"bdd")
--debug Turn on various internal debugging output
-h, --help display help for command
Another goddamned test library, dear god, why
Yes it seems annoying that I'm adding to an existing well-trodden ground of testkits here but I was frustrated at some lacking features, namely:
- No testkit seems to be able to do pre-dependencies correctly - what if one test requires another first? Its common to login or negotiate Auth credentials for some test units, why is the only way to do this screwing around with
before()blocks or nesting tests? - No testkit I've seen puts parallelism first and foremost rather than an afterthought. This library is all about parallel with serial functionality as a secondary choice.
- Context is outdated - arrow functions should be universal when declaring tests, no need to differentiate between
test(()=> {})andtest(function() {})contexts, just accept a universal context as an argument and work from there. This makes stuff like usingt.timeout()ort.skip()much easier without having to care about a "strong" function context rather than arrow functions. beforeEach()/afterEach()are anti-patterns and should not be supported - especially when we are doing things in parallel.- Why can't we say why a test was skipped with
.skip()? chai/expect()should ship as standard - yes choice is nice but if thats what everyone uses anyway why bother adding another dependency + import header.- Tests should support sub-stages (see
TestaContext.stage()) to clearly denote where in a long-running or complex test we are up to - Tests should be able to easily dump information for inspection without just spewing to the console (see
TestContext.dump()) - Tests can still be serialized on an opt-in basis using
.serial()- or use--serialto force all tests to do this
The main test instanciator. Returns a Testa class instance.
Utility function which exposes a chai#expect function.
import test from '@momsfriendlydevco/testa';
test('simple test', ()=> {
test.expect(1).to.be.ok;
test.expect(1).to.be.a('number');
});A Testa class instance.
Specify an ID for a test. Returns the chainable instance. Used to specify dependencies or refer to tests.
Indicate the location of the test.
This is automatically populated when using the testa bin.
Returns the chainable instance.
Specify the test worker function. Returns the chainable instance.
Alias for Testa.handler()
Specify a human readable title for a test. Used during logging. Returns the chainable instance.
Add a more verbose description for a test. Returns the chainable instance.
Mark a test for skipping, these will not be run but marked as skipped when logging. Returns the chainable instance.
Mark a test for 'only' inclusion. Unless overridden these will be the only tests run. Returns the chainable instance.
Set the priority order of a function.
Level can be a number (higher numbers run first) or a meta string such as 'BEFORE', 'AFTER'
Returns the chainable instance.
The before() and after() functions are really just aliases of test.priority('BEFORE', ...)
Set a pre-dependency for a test. This marked test will not run less the dependency has run and successfully resolved first. Returns the chainable instance.
Alias for Testa.depends(...string)
Set a post-dependency for a test. This queues up a reverse dependency where the named tests will execute after this test. Returns the chainable instance.
Alias for Testa.postDepends(...string)
Alias for test().priority('BEFORE', ...)
Returns the chainable instance.
Alias for test().priority('AFTER', ...)
Returns the chainable instance.
Set the amount of time before a test is considered slow to resolve. Can be a raw millisecond time or any valid Timestring. Returns the chainable instance.
Set the amount of time before a test should timeout. Can be a raw millisecond time or any valid Timestring. Returns the chainable instance.
Actual test runner. Creates a TestaContext, runs the handler function with that context and handles errors and general logging.
Adds one or more IDs as a pre-dependency before running the test. These must resolve successfully before being able to continue. Can be specified multiple times. Returns the chainable instance.
Mark this test as requiring an isolated in-series runner. This differs from regular tests in that its force to run in a series rather than in massive-parallel - the default Testa behaviour. Returns the chainable instance.
Context object passed as the functional context + the only argument to all test handler functions.
Log some test output.
Returns the chainable TestaContext instance.
Log some test output as a warning but don't exit the test.
Returns the chainable TestaContext instance.
Log some arbitrary output and continue the test.
This is designed mainly for large complex objects which may need to be dissected separately.
Returns the chainable TestaContext instance.
Signal that we are at a specific sub-stage within a test function.
In most cases this acts as a bookmark.
Returns the chainable TestaContext instance.
Notify that a test was skipped and indicate why.
Returns the chainable TestaContext instance.
Wrapper around timestring() + setTimeout() to wait for an arbitrary amount of time.
Returns a promise which will resolve when the delay has elapsed.