diff --git a/README.md b/README.md index 30f48a5..aa0c641 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ ## Requirements & Usage -`Gunner` uses very modern JavaScript, and hence requires node 10+ currently. +`Gunner` uses very modern JavaScript, and hence requires **node 10+** ❗️⚠️ currently. -Create a new `Gunner` instance and simply write your tests. The assertion methods are passed in as the callback to the test function. +Create a new `Gunner` instance and simply write your tests. The assertion methods are passed in as the callback as an `expect` object to the test function. ```JavaScript const gunner = new Gunner(); @@ -22,14 +22,28 @@ gunner.test('arrays are equal', expect => { gunner.run(); ``` -## API +## Documentation Index + +- ### `Class`: + - #### [`Gunner.constructor`](#new-gunner-options) + +- ### `Methods`: + - #### [`Gunner#test`](#gunnertest-title-implementation) + - #### [`Gunner#before`](#gunnerbefore-title-implementation) + - #### [`Gunner#after`](#gunnerafter-title-implementation) + - #### [`Gunner#run`](#gunnerrun-options) + +- ### `Constants`: + - #### `[Gunner.Start]` + - #### `[Gunner.End]` + +- ### [`State and Advanced Usage`](#state) -#### [Gunner.constructor](#new-gunner) -#### [Gunner#test(title, implementation)](#gunnertest) -#### [Gunner#run(options)](#gunnerrun) --- -### new Gunner +## API + +### new Gunner (options) Creates a new Gunner instance. @@ -37,17 +51,21 @@ Creates a new Gunner instance. - **`name`** [default: undefined]: A name for this Gunner instance. -#### Usage +#### Example ```JavaScript const gunner = new Gunner(options); ``` -### Gunner#test +[`INDEX`](#index) + +### Gunner#test (title, implementation) + +Registers a new test. An `expect` object is passed into the implementation callback as the first argument. A test can have multiple expect statements. They should be returned as an array. The first expect to fail will cause the test to fail. -Registers a new test. A test can have multiple expect statements. They should be returned as an array. The first expect to fail will cause the test to fail. +The `expect` object is passed in as first argument, but any assertion module may be used, as long it either throws an error, or rejects. If you use a different assert module such as `chai`, remember to return Promises properly, else some Promises will be lost, just like in regular JavaScript. -#### Usage +#### Example ```JavaScript gunner.test('sum should equal 3', expect => { @@ -86,7 +104,75 @@ gunner.test('asynchronous test', async expect => { }) ``` -### Gunner#run +[`INDEX`](#index) + +### Gunner#before (title, implementation) + +Registers a new `before` hook. `before` hooks run before the selected test(s). The implementation callback is similar to that of a test, with the exception that no expect object will be passed. + +The first argument can be one of: + +- title of a test, which causes the hook to run once before the mentioned test + +- `'*'`, which causes the hook to run once before _every_ test + +- either of the constants: `Gunner.Start`, and `Gunner.End`. + +`gunner.before(Gunner.Start, () => {})` will run once before Gunner starts running any tests. The `Gunner.End` equivalent will run once after running all tests (before ending). + +#### Example + +```JavaScript +gunner.before('insert to db should not error', () => { + + // Clear db before test + return db.remove('users', { username: 'mkrhere' }); + +}); + +gunner.test('insert to db should not error', expect => { + + const user = await db.insert({ + username: 'mkrhere', + firstname: 'muthu', + }); + return expect(user).hasPair('firstname', 'muthu'); + +}); +``` + +[`INDEX`](#index) + +### Gunner#after (title, implementation) + +Registers a new `after` hook. `after` hooks run after the corresponding test(s). The implementation callback is similar to that of a test, with the exception that no expect object will be passed. + +The first argument is similar to `Gunner#before`, but does not accept `Gunner.Start` and `Gunner.End` constants, only `'*'` or test description. + +#### Example + +```JavaScript +gunner.test('insert to db should not error', expect => { + + const user = await db.insert({ + username: 'mkrhere', + firstname: 'muthu', + }); + return expect(user).hasPair('firstname', 'muthu'); + +}); + +gunner.after('insert to db should not error', () => { + + // Clear db after test + return db.remove('users', { username: 'mkrhere' }); + +}); +``` + +[`INDEX`](#index) + +### Gunner#run (options) Starts running Gunner tests. Takes an options object as optional parameter. @@ -95,9 +181,73 @@ Starts running Gunner tests. Takes an options object as optional parameter. - **`log`** [default: true]: Turn logs on or off (returns array of results) - **`trace`** [default: false]: Turn stack traces on or off -#### Usage +#### Example ```JavaScript const options = { logs: true, trace: true }; gunner.run(options); ``` + +[`INDEX`](#index) + +### State + +> `[ADVANCED]` + +Additionally, `before` hooks create state objects from returned values that will be passed down hierarchically to other `before` and `after` hooks, and their matching tests. The state object is passed as second argument to tests. Hooks will also receive as the first argument state from hooks above itself. + +This has four levels: + +- `'@start'` (from the `Gunner.Start` hooks). +- `'@every'` (from the `'*'` hooks). +- `'@this'` (from the hook registered to this test). +- `'@results'` (results from all tests, passed only to the `Gunner.End` hook). + +#### Example + +```JavaScript +gunner.before(Gunner.Start, () => { + const db = DBModule.createDbConnection(); + return db; +}); + +gunner.before('test user should exist in db', state => { + + // Receives '@start' and '@every' states if exists + const db = state['@start'][0]; + + const testUser = await db.insert('users', { + username: 'mkrhere', + firstname: 'muthu', + }); + return testUser.username; + +}); + +gunner.test('test user should exist in db', (expect, state) => { + + // Receives '@start', '@every', and '@this' states + // Each state level is an array because multiple hooks may exist per level + const db = state['@start'][0]; + const username = state['@this'][0]; + + const user = await db.find('users', { username }); + return expect(user).hasPair('firstname', 'muthu'); + +}); + +gunner.after('test user should exist in db', state => { + + const db = state['@start'][0]; + const db = state['@this'][0]; + + return db.remove('users', { username }); + +}); +``` + +[`INDEX`](#index) + +## Credits + +`Gunner` was built at [Klenty](https://klenty.com), a sales automation startup, by Muthu Kumar [(@MKRhere)](https://github.com/MKRhere). diff --git a/hello.txt b/sample/hello.txt similarity index 100% rename from hello.txt rename to sample/hello.txt diff --git a/sample.test.js b/sample/sample.test.js similarity index 95% rename from sample.test.js rename to sample/sample.test.js index 73fef4b..9d83ef2 100644 --- a/sample.test.js +++ b/sample/sample.test.js @@ -3,7 +3,7 @@ * used during development */ -const Gunner = require('./index.js'); +const Gunner = require('../index.js'); const gunner = new Gunner({ name: 'sample tests' }); const a = 1; @@ -38,7 +38,7 @@ gunner.after( gunner.test('file must have hello as content', async expect => { const { readFile } = require('fs').promises; - const file = await readFile('./hello.txt', { encoding: 'utf8' }); + const file = await readFile(__dirname + '/hello.txt', { encoding: 'utf8' }); return [ expect(file).equal('hello'), expect(file.length).equal(5),