npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

eltro

v1.6.1

Published

Eltro is a tiny no-dependancy test framework for node

Downloads

147

Readme

eltro Build status

Eltro is a no-nonsense, no dependancy, small test framework created to use in node 13 or higher using ECM modules.

Installation

Install with npm globally:

$ npm install --global eltro

or as a development dependency for your project:

$ npm install --save-dev eltro

Getting started

$ npm install --save-dev eltro
$ mkdir test

Next in your favourite editor, create test/test.mjs:

import { Eltro as t, assert} from 'eltro'

t.describe('Array', function() {
  t.before(function() {
    // Prepare our test if needed
  })

  t.describe('#indexOf()', function() {
    t.test('should return -1 when value is not present', function() {
      assert.equal([1,2,3].indexOf(4), -1)
    })
  })

  t.after(function() {
    // Cleanup after if needed
  })
})

Set up a test script in package.json:

"scripts": {
  "test": "eltro"
}

Then run tests with:

$ npm test


  test/test.mjs
    √ Array #indexOf() should return -1 when value is not present


  1 passing (3ms)

Watch

You can also run eltro in watch mode. Update your package.json and add the following:

{
  /* ... */
  "scripts": {
    "test": "eltro",
    "test:watch": "eltro --watch my_watch_name",
  },
  "watch": {
    "my_watch_name": {
      "patterns": [ "src", "test" ],
      "extensions": "js,mjs"
    }
  },
  /* ... */
}

Then add --watch my_watch_name to your eltro command (as seen in the above example) and you're good to go:

$ npm test:watch


  test/test.mjs
    √ Array #indexOf() should return -1 when value is not present


  1 passing (3ms)

[my_watch_name] 09:49:38: Ran successfully. Waiting for file changes before running again...

You can also run your own npm command while using the eltro file watcher like so:

$ eltro --watch my_watch_name --npm build

Assertions

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 with a few extra methods:

  • assert.equalWithMargin(value, test, margin, [message]): Check if number value is equal to test with error margin.
  • 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.throwsAndCatch(fn, [message]): Checks if function fn throws and returns the thrown error.
  • assert.isFulfilled(promise, [message]): Assert the promise resolves.
  • assert.isRejected(promise, [message]): Assert the promise gets rejects.

Asynchronous Code

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.

Example of testing using done:

import { Eltro as t, assert} from 'eltro'

t.describe('User', function() {
  t.describe('#save()', function() {
    t.test('should save without error', function(done) {
      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):

import { Eltro as t, assert} from 'eltro'

t.describe('User', function() {
  t.describe('#save()', function() {
    t.test('should save without error', function(done) {
      var user = new User('Luna')
      user.save(done)
    })
  })
})

Or another alternative is to use promises and return a promise directly:

import { Eltro as t, assert} from 'eltro'

t.test('should complete this test', function(done) {
  return new Promise(function(resolve, reject) {
    reject(new Error('Uh oh, something went wrong'))
  }).then(done)
})

Which works well with async/await like so:

t.test('async test', async function() {
  let user = await User.find({ username: 'test' })
  assert.ok(user)
})

Spying and stubbing

Inspired by sinon js, this library comes with pre-built simple sinon-like style spy() and stub()

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)

Api

t.test(message, func)

Queue up the func as a test with the specified messagt.

t.describe(message, func)

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:

import { Eltro as t, assert} from 'eltro'

function someFunction() { return true }

t.describe('#someFunction()', function() {
  t.test('should always return true', function() {
    assert.strictEqual(someFunction(), true)
    assert.strictEqual(someFunction(), true)
    assert.strictEqual(someFunction(), true)
  })
})

will output:

    √ #someFunction() should always return true

t.before(func)

Queue up the func to run before any test or groups within current active group.

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.

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)
  })
})

t.beforeEach(func)

Queue up the func to run before each test or groups within current active group.

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.

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)
  })
})

t.only()

Eltro supports exclusivity when running tests. When specified, only tests marked with only will be run.

You can do exclusivity on tests by adding .only() in front of describe, after or before the test like so:

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

t.test('Only run this test', function() {
  assert.strictEqual(true, true)
}).only()

or like so:

t.only().test('Only run this test', function() {
  assert.strictEqual(true, true)
})

t.skip()

You can skip tests easily by adding .skip() before describe, before or after the test like so:

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:

t.test('Skip due to something being broken', function() {
  BrokenFunction()
}).skip()

or like so:

t.skip().test('Skip this', function() { ... })

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:

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() { ... })
})

Or apply to individual test like so:

t.test('This is a really long test', async function() {
  await DoSomethingForReallyLongTime()
}).timeout(5000) // 5 seconds

or like so:

t.timeout(5000).test('A long test', async function() { ... })

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:

import { assert } from 'eltro'

assert.notOk(value[, message])

Tests if value is a falsy value using Boolean(value) == false

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

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

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.

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

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')

assert.throwsAndCatch(fn[, message])

Tests to make sure the function throws an exception. The important feature is this returns the original error that was thrown.

let err = assert.throwsAndCatch(() => { throw new Error('hello world') }) // ok
assert.strictEqual(err.message, 'hello world')

Sinon-like spy() stub()

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:

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:

let spying = spy()
spying('hello', 'world')

assert.strictEqual(spying.lastCall[0], 'hello')
assert.strictEqual(spying.lastCall[1], 'world')

lastCall

Returns the last call that was made to the spy or stub:

let spying = spy()
spying('a')
spying('b')
spying('c')

assert.strictEqual(spying.lastCall[0], 'c')

called

Boolean variable that gets flipped once it gets called at least once

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

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.

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.

let fn = stub()
fn.throws(new Error('b'))

try {
  fn()
} catch (err) {
  assert.strictEqual(err.message, 'b')
}

resolves(data)

Specifies what value the stub or spy should return wrapped in a promise.

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.

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.

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)

getCall(index) getCallN(num)

Get a specific call. The getCall gets a zero-based index call while the getCallN(num) gets the more natural number call

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')

onCall(index) onCallN(num)

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.

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.

let evnt = stub()

evnt('onclick',   'one')
evnt('onerror',   'two')
evnt('something', 'three')
evnt('onpress',   'four')
evnt('else',      'five')

let foundPressCall = evnt.findCall(function(call) { return call[0] === 'onpress' })
assert.strictEqual(foundPressCall[0], 'onpress')
assert.strictEqual(foundPressCall[1], 'four')