2022-01-11 09:44:42 +00:00
# eltro [![Build status](https://ci.nfp.is/api/projects/status/n7ufp6gi48rc3bs9?svg=true)](https://ci.nfp.is/project/AppVeyor/eltro)
2021-06-02 12:08:22 +00:00
Eltro is a no-nonsense, no dependancy, small test framework created to use in node 13 or higher using ECM modules.
2020-03-31 17:21:36 +00:00
# Installation
Install with npm globally:
```bash
2020-04-01 15:50:55 +00:00
$ npm install --global eltro
2020-03-31 17:21:36 +00:00
```
or as a development dependency for your project:
```bash
2020-04-01 15:50:55 +00:00
$ npm install --save-dev eltro
2020-03-31 17:21:36 +00:00
```
# Getting started
```bash
2020-04-01 15:50:55 +00:00
$ npm install --save-dev eltro
2020-03-31 17:21:36 +00:00
$ mkdir test
```
2021-06-02 12:08:22 +00:00
Next in your favourite editor, create `test/test.mjs` :
2020-03-31 17:21:36 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
import { Eltro as t, assert} from 'eltro'
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
t.describe('Array', function() {
2021-06-18 02:06:08 +00:00
t.before(function() {
// Prepare our test if needed
})
2020-04-01 15:50:55 +00:00
t.describe('#indexOf()', function() {
t.test('should return -1 when value is not present', function() {
2020-03-31 17:21:36 +00:00
assert.equal([1,2,3].indexOf(4), -1)
})
})
2021-06-18 02:06:08 +00:00
t.after(function() {
// Cleanup after if needed
})
2020-03-31 17:21:36 +00:00
})
```
2021-06-02 12:08:22 +00:00
Set up a test script in package.json:
2020-03-31 17:21:36 +00:00
```json
"scripts": {
2021-06-02 12:08:22 +00:00
"test": "eltro"
2020-03-31 17:21:36 +00:00
}
```
Then run tests with:
```bash
$ npm test
test/test.mjs
√ Array #indexOf () should return -1 when value is not present
1 passing (3ms)
```
# Assertions
2020-04-01 15:50:55 +00:00
Not only does eltro allow you to use any assertion library of your own choosing, it also comes with it's own assertion library based on node's default [assert ](https://nodejs.org/api/assert.html ) with a few extra methods:
2020-03-31 17:21:36 +00:00
* `assert.notOk(value, [message])` : Assert value is not ok.
* `assert.match(value, test, [message])` : Check if value matches RegExp test.
* `assert.notMatch(value, [message])` : Check if value does not match RegExp test.
* `assert.isFulfilled(promise, [message])` : Assert the promise resolves.
* `assert.isRejected(promise, [message])` : Assert the promise gets rejects.
2023-08-26 21:06:21 +00:00
# Asynchronous Code
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
Eltro supports any type of asynchronous code testing. It can either be done by adding a parameter to the function (usually done) that gets called once the tests done but eltro also supports promises.
2020-03-31 17:21:36 +00:00
Example of testing using done:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
import { Eltro as t, assert} from 'eltro'
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
t.describe('User', function() {
t.describe('#save()', function() {
t.test('should save without error', function(done) {
2020-03-31 17:21:36 +00:00
var user = new User('Luna')
user.save(function(err) {
if (err) done(err)
else done()
})
})
})
})
```
Alternatively, just use the done() callback directly (which will handle an error argument, if it exists):
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
import { Eltro as t, assert} from 'eltro'
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
t.describe('User', function() {
t.describe('#save()', function() {
t.test('should save without error', function(done) {
2020-03-31 17:21:36 +00:00
var user = new User('Luna')
user.save(done)
})
})
})
```
Or another alternative is to use promises and return a promise directly:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
import { Eltro as t, assert} from 'eltro'
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
t.test('should complete this test', function(done) {
2020-03-31 17:21:36 +00:00
return new Promise(function(resolve, reject) {
reject(new Error('Uh oh, something went wrong'))
}).then(done)
})
```
Which works well with `async/await` like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
t.test('async test', async function() {
2020-03-31 17:21:36 +00:00
let user = await User.find({ username: 'test' })
assert.ok(user)
})
```
2023-08-26 21:06:21 +00:00
# Spying and stubbing
Inspired by sinon js, this library comes with pre-built simple sinon-like style spy() and stub()
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
import { assert, spy, stub } from 'eltro'
let myFunc = spy()
let myStub = stub()
myFunc(1)
myFunc(2)
myFunc(3)
myStub.returns('world')
let out = myStub('hello')
assert.strictEqual(out, 'world')
assert.strictEqual(myFunc.getCall(0), 1)
assert.strictEqual(myFunc.getCall(1), 2)
assert.strictEqual(myFunc.getCall(2), 3)
assert.strictEqual(myFunc.callCount, 3)
assert.strictEqual(myStub.callCount, 1)
```
2020-03-31 17:21:36 +00:00
# Api
2020-04-01 15:50:55 +00:00
### t.test(message, func)
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
Queue up the `func` as a test with the specified messagt.
2020-03-31 17:21:36 +00:00
2020-04-01 15:50:55 +00:00
### t.describe(message, func)
2020-03-31 17:21:36 +00:00
In case you wanna describe a bunch of tests, you can add them inside `func` and it will have the specified `message` prepended before every test:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
import { Eltro as t, assert} from 'eltro'
2020-03-31 17:21:36 +00:00
function someFunction() { return true }
2021-06-18 02:06:08 +00:00
t.describe('#someFunction()', function() {
2020-04-01 15:50:55 +00:00
t.test('should always return true', function() {
2020-03-31 17:21:36 +00:00
assert.strictEqual(someFunction(), true)
assert.strictEqual(someFunction(), true)
assert.strictEqual(someFunction(), true)
2021-06-18 02:06:08 +00:00
})
2020-03-31 17:21:36 +00:00
})
```
will output:
```bash
√ #someFunction () should always return true
```
2021-06-18 02:06:08 +00:00
### t.before(func)
Queue up the `func` to run before any test or groups within current active group.
2023-08-26 21:10:56 +00:00
```javascript
2021-06-18 02:06:08 +00:00
import { Eltro as t, assert} from 'eltro'
t.before(function() {
// Prepare something before we start any of the below tests
})
t.describe('#myTest()', function() {
t.before(function() {
// Runs before the test below
})
t.test('true should always be true', function() {
assert.strictEqual(true, true)
})
})
t.describe('#anotherTest()', function() {
t.before(function() {
// Runs before the test below
})
t.test('false should always be false', function() {
assert.strictEqual(false, false)
})
})
```
### t.after(func)
Queue up the `func` to run after any test or groups within current active group.
2023-08-26 21:10:56 +00:00
```javascript
2021-06-18 02:06:08 +00:00
import { Eltro as t, assert} from 'eltro'
t.after(function() {
// After we finish all the tests below, this gets run
})
t.describe('#myTest()', function() {
t.after(function() {
// Runs after the test below
})
t.test('true should always be true', function() {
assert.strictEqual(true, true)
})
})
t.describe('#anotherTest()', function() {
t.after(function() {
// Runs after the test below
})
t.test('false should always be false', function() {
assert.strictEqual(false, false)
})
})
```
2023-08-26 21:06:21 +00:00
### t.beforeEach(func)
Queue up the `func` to run before each test or groups within current active group.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
import { Eltro as t, assert} from 'eltro'
t.beforeEach(function() {
// Prepare something before each of the following tests
})
t.describe('#myTest()', function() {
t.beforeEach(function() {
// Runs before every test in this group
})
t.test('true should always be true', function() {
assert.strictEqual(true, true)
})
})
t.describe('#anotherTest()', function() {
t.beforeEach(function() {
// Runs before every test in this group
})
t.test('false should always be false', function() {
assert.strictEqual(false, false)
})
})
```
### t.afterEach(func)
Queue up the `func` to run after every test or groups within current active group.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
import { Eltro as t, assert} from 'eltro'
t.afterEach(function() {
// After we finish each individual test below, this gets run
})
t.describe('#myTest()', function() {
t.afterEach(function() {
// Runs after each text in this group
})
t.test('true should always be true', function() {
assert.strictEqual(true, true)
})
})
t.describe('#anotherTest()', function() {
t.afterEach(function() {
// Runs after each text in this group
})
t.test('false should always be false', function() {
assert.strictEqual(false, false)
})
})
```
2020-04-01 17:16:46 +00:00
### t.only()
2020-04-01 16:42:13 +00:00
Eltro supports exclusivity when running tests. When specified, only tests marked with only will be run.
2020-04-01 17:16:46 +00:00
You can do exclusivity on tests by adding `.only()` in front of describe, after or before the test like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 17:16:46 +00:00
t.only().describe('Only these will run', function() {
t.test('this one', function() { assert.strictEqual(true, true) })
t.test('and this one', function() { assert.strictEqual(true, true) })
})
```
You can also put it on individual test like so
2020-04-01 16:42:13 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 16:42:13 +00:00
t.test('Only run this test', function() {
assert.strictEqual(true, true)
}).only()
```
or like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 16:42:13 +00:00
t.only().test('Only run this test', function() {
assert.strictEqual(true, true)
})
```
2020-04-01 17:16:46 +00:00
### t.skip()
2020-03-31 17:21:36 +00:00
2020-04-01 17:16:46 +00:00
You can skip tests easily by adding `.skip()` before describe, before or after the test like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 17:16:46 +00:00
t.skip().describe('None of these will run', function() {
t.test('not this', function() { assert.strictEqual(true, true) })
t.test('or this one', function() { assert.strictEqual(true, true) })
})
```
You can also do it on individual tests like so:
2020-03-31 17:21:36 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
t.test('Skip due to something being broken', function() {
2020-03-31 17:21:36 +00:00
BrokenFunction()
}).skip()
```
2020-04-01 16:42:13 +00:00
or like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 16:42:13 +00:00
t.skip().test('Skip this', function() { ... })
```
2020-04-01 17:16:46 +00:00
### t.timeout(dur)
Tests can take a long time. By default, eltro will cancel a test if it takes longer than 2 seconds. You can however override this by calling the timeout function after or before the test or before the describe with the specified duration in milliseconds like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 17:16:46 +00:00
t.timeout(5000).describe('These will all have same timeout', function() {
t.test('One slow function', async function() { ... })
t.test('Another slow function', async function() { ... })
})
```
2020-03-31 17:21:36 +00:00
2020-04-01 17:16:46 +00:00
Or apply to individual test like so:
2020-03-31 17:21:36 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 15:50:55 +00:00
t.test('This is a really long test', async function() {
2020-03-31 17:21:36 +00:00
await DoSomethingForReallyLongTime()
}).timeout(5000) // 5 seconds
```
2020-04-01 16:42:13 +00:00
or like so:
2023-08-26 21:10:56 +00:00
```javascript
2020-04-01 16:42:13 +00:00
t.timeout(5000).test('A long test', async function() { ... })
```
2023-08-26 21:06:21 +00:00
# Assert
Eltro comes with an extended version of node's built-in assertion library.
You can start using them by simply importing it with eltro test runner:
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
import { assert } from 'eltro'
```
### assert.notOk(value[, message])
Tests if value is a falsy value using `Boolean(value) == false`
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
assert.notOk(false) // ok
assert.notOk(null) // ok
assert.notOk(undefined) // ok
assert.notOk([]) // throws
```
### assert.match(value, test[, message])
Test if the string value has a regex match of test
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
assert.match('asdf', /a/) // ok
assert.match('hello world', /hello/) // ok
assert.match('something', /else/) // throws
```
### assert.notMatch(value, test[, message])
Test if the string value does not regex match the test
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
assert.notMatch('asdf', /b/) // ok
assert.notMatch('something', /else/) // ok
assert.notMatch('hello world', /hello/) // throws
```
### assert.isFulfilled(promise[, message])
Tests to make sure the promise gets fulfilled successfully and
returns the final result.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
await assert.isFulfilled(Promise.resolve(null)) // ok
await assert.isFulfilled(() => { throw new Error() }) // throws
```
### assert.isRejected(promise[, message])
Tests to make sure the promise gets rejected and returns the error
or value that was rejected
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let val = await assert.isRejected(Promise.reject('asdf')) // ok
assert.strictEqual(val, 'asdf')
let err = await assert.isRejected(() => { throw new Error('hello') }) // ok
assert.strictEqual(err.message, 'hello')
```
2023-08-26 21:10:56 +00:00
# Sinon-like spy() stub()
2023-08-26 21:06:21 +00:00
Using sinon-inspired mechanics for spying on calls as well as being able
to stub existing functionality, eltro comes with a handy little copy-cat.
Functionality-wise, the difference between spy() and stub() are none.
Both will do the exact same thing, the naming differention is simply to allow
the resulting code to speak about its purpose.
To create a stub or a spy, simply import it and call it like so:
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
import { spy, stub } from 'eltro'
let spying = spy()
let fn = stub()
```
Each call to stub or spy is an array list of the passed-on arguments:
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let spying = spy()
spying('hello', 'world')
assert.strictEqual(spying.lastCall[0], 'hello')
assert.strictEqual(spying.lastCall[1], 'world')
```
### lastCall
2023-08-26 21:10:56 +00:00
Returns the last call that was made to the spy or stub:
2023-08-26 21:06:21 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let spying = spy()
spying('a')
spying('b')
spying('c')
assert.strictEqual(spying.lastCall[0], 'c')
```
### called
2023-08-26 21:10:56 +00:00
Boolean variable that gets flipped once it gets called at least once
2023-08-26 21:06:21 +00:00
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let spying = spy()
assert.strictEqual(spying.called, false)
spying('a')
assert.strictEqual(spying.called, true)
spying('b')
spying('c')
assert.strictEqual(spying.called, true)
```
### callCount
The number of times it's been called
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let spying = spy()
assert.strictEqual(spying.callCount, 0)
spying('a')
assert.strictEqual(spying.callCount, 1)
spying('b')
spying('c')
assert.strictEqual(spying.callCount, 3)
```
### returns(data)
Specifies what value the stub or spy should return when it gets called.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fn = stub()
fn.returns('a')
assert.strictEqual(fn(), 'a')
assert.strictEqual(fn(), 'a')
```
### throws(data)
Specifies what value the stub or spy should throw when it gets called.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fn = stub()
fn.throws(new Error('b'))
try {
fn()
} catch (err) {
2023-08-26 21:10:56 +00:00
assert.strictEqual(err.message, 'b')
2023-08-26 21:06:21 +00:00
}
```
### resolves(data)
Specifies what value the stub or spy should return wrapped in a promise.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fn = stub()
fn.resolves('a')
fn().then(function(data) {
assert.strictEqual(data, 'a')
})
```
### rejects(data)
Specifies what value the stub or spy should reject, wrapped in a promise.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fn = stub()
fn.rejects('nope')
fn().catch(function(data) {
assert.strictEqual(data, 'nope')
})
```
### returnWith(fn)
Specify custom function to be called whenever the stub or spy gets called.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fn = stub()
fn.returnWith(function(a) {
if (a === 'a') return true
return false
})
assert.strictEqual(fn(), false)
assert.strictEqual(fn('b'), false)
assert.strictEqual(fn('a'), true)
assert.strictEqual(fn.callCount, 3)
```
2023-08-26 21:10:56 +00:00
### getCall(index) getCallN(num)
2023-08-26 21:06:21 +00:00
Get a specific call. The `getCall` gets a zero-based index call while the `getCallN(num)` gets the more natural number call
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let spying = spy()
spying('a')
spying('b')
spying('c')
assert.strictEqual(spying.getCall(0), 'a')
assert.strictEqual(spying.getCall(1), 'b')
assert.strictEqual(spying.getCallN(1), 'a')
assert.strictEqual(spying.getCallN(2), 'b')
```
2023-08-26 21:10:56 +00:00
### onCall(index) onCallN(num)
2023-08-26 21:06:21 +00:00
Overwrite behavior for a specific numbered call. Just like with getCall/getCallN, the onCall is zero-indexed number of the call you want to specify custom behavior while onCallN is the more natural number of the call you want to specify custom behavior.
Note, when called with null, it specifies the default behavior.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let fnOne = stub()
let fnTwo = stub()
fnOne.onCall(1).returns('b')
.onCall().returns('a')
fnTwo.onCallN(2).returns('two')
.onCallN().returns('one')
assert.strictEqual(fnOne(), 'a')
assert.strictEqual(fnOne(), 'b')
assert.strictEqual(fnOne(), 'a')
assert.strictEqual(fnTwo(), 'one')
assert.strictEqual(fnTwo(), 'two')
assert.strictEqual(fnTwo(), 'one')
```
### findCall(fn)
Search for the first call when `fn(call)` returns `true` . Essentially a filter to search for a specific call that matches whatever call you're searching for.
2023-08-26 21:10:56 +00:00
```javascript
2023-08-26 21:06:21 +00:00
let evnt = stub()
2023-08-26 21:10:56 +00:00
evnt('onclick', 'one')
evnt('onerror', 'two')
2023-08-26 21:06:21 +00:00
evnt('something', 'three')
2023-08-26 21:10:56 +00:00
evnt('onpress', 'four')
evnt('else', 'five')
2023-08-26 21:06:21 +00:00
let foundPressCall = evnt.findCall(function(call) { return call[0] === 'onpress' })
assert.strictEqual(foundPressCall[0], 'onpress')
assert.strictEqual(foundPressCall[1], 'four')
```