sinon: Add helper function findCall()
continuous-integration/appveyor/branch AppVeyor build succeeded Details

update readme quite a bit
master
Jonatan Nilsson 2023-08-26 21:06:21 +00:00
parent 9d2b71339c
commit a2638b671d
3 changed files with 390 additions and 2 deletions

356
README.md
View File

@ -74,8 +74,8 @@ Not only does eltro allow you to use any assertion library of your own choosing,
* `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.
# Asynchronous Code
# 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.
@ -133,6 +133,30 @@ t.test('async test', async function() {
})
```
# Spying and stubbing
Inspired by sinon js, this library comes with pre-built simple sinon-like style spy() and stub()
```node
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)
@ -227,6 +251,70 @@ t.describe('#anotherTest()', function() {
})
```
### t.beforeEach(func)
Queue up the `func` to run before each test or groups within current active group.
```node
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.
```node
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.
@ -305,3 +393,267 @@ or like so:
```node
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:
```node
import { assert } from 'eltro'
```
### assert.notOk(value[, message])
Tests if value is a falsy value using `Boolean(value) == false`
```node
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
```node
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
```node
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.
```node
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
```node
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')
```
# 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:
```node
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:
```node
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 sinon and stub:
```node
let spying = spy()
spying('a')
spying('b')
spying('c')
assert.strictEqual(spying.lastCall[0], 'c')
```
### called
Boolean variable that gets flipped once it's called at least once
```node
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
```node
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.
```node
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.
```node
let fn = stub()
fn.throws(new Error('b'))
try {
fn()
} catch (err) {
assert.strictEqual(fn(), 'b')
}
```
### resolves(data)
Specifies what value the stub or spy should return wrapped in a promise.
```node
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.
```node
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.
```node
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
```node
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.
```node
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.
```node
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')
```

View File

@ -35,6 +35,13 @@ export function stub(returnFunc = null) {
func.called = false
func.callCount = 0
func.findCall = function(fn) {
for (let call of calls) {
if (fn(call)) return call
}
return null
}
func.getCall = function(i) {
return calls[i]
}

View File

@ -96,6 +96,35 @@ import { spy, stub } from '../index.mjs'
assert.strictEqual(spyer.thirdCall[0], assertThirdArgs[0])
assert.strictEqual(spyer.thirdCall[1], assertThirdArgs[1])
})
t.test('should support searching for a call', function() {
const assertFirstArgs = 'asdf'
const assertSecondArgs = { a: 1 }
const assertThirdArgs = [{ b: 1 }, { c: 2 }]
let spyer = tester()
assert.notOk(spyer.called)
spyer(assertFirstArgs)
spyer(assertSecondArgs)
spyer(assertThirdArgs[0], assertThirdArgs[1])
let call = spyer.findCall((args) => args[0] === assertSecondArgs)
assert.strictEqual(spyer.secondCall, call)
assert.strictEqual(call[0], assertSecondArgs)
call = spyer.findCall((args) => args[0].b === assertThirdArgs[0].b)
assert.strictEqual(spyer.thirdCall, call)
assert.strictEqual(call[0], assertThirdArgs[0])
assert.strictEqual(call[1], assertThirdArgs[1])
call = spyer.findCall((args) => typeof args[0] === 'string')
assert.strictEqual(spyer.firstCall, call)
assert.strictEqual(call[0], assertFirstArgs)
call = spyer.findCall(() => false)
assert.strictEqual(call, null)
})
})
})