diff --git a/index.mjs b/index.mjs index 822968b..d0a1ebb 100644 --- a/index.mjs +++ b/index.mjs @@ -1,7 +1,10 @@ import Eltro from './lib/eltro.mjs' import assert from './lib/assert.mjs' +import { spy, stub } from './lib/sinon.mjs' export { Eltro, assert, + spy, + stub, } diff --git a/lib/sinon.mjs b/lib/sinon.mjs new file mode 100644 index 0000000..2feaaa4 --- /dev/null +++ b/lib/sinon.mjs @@ -0,0 +1,55 @@ +const indexMap = [ + 'firstCall', + 'secondCall', + 'thirdCall', +] + +export function spy() { + return stub() +} + +export function stub(returnFunc = null) { + if (returnFunc && typeof(returnFunc) !== 'function') { + throw new Error('stub() was called with non-function argument') + } + let returner = returnFunc ? returnFunc : null + let calls = [] + let func = function(...args) { + func.called = true + calls.push(args) + if (func.callCount < indexMap.length) { + func[indexMap[func.callCount]] = args + } + func.callCount++ + if (returner) { + return returner(...args) + } + } + func.called = false + func.callCount = 0 + func.onCall = function(i) { + return calls[i] + } + func.returns = function(data) { + returner = function() { return data } + } + func.returnWith = function(returnFunc) { + if (typeof(returnFunc) !== 'function') { + throw new Error('stub() was called with non-function argument') + } + returner = returnFunc + } + func.throws = function(data) { + returner = function() { throw data } + } + func.resolves = function(data) { + returner = function() { return Promise.resolve(data) } + } + func.rejects = function(data) { + returner = function() { return Promise.reject(data) } + } + for (let i = 0; i < indexMap.length; i++) { + func[indexMap[i]] = null + } + return func +} diff --git a/test/sinon.test.mjs b/test/sinon.test.mjs new file mode 100644 index 0000000..c80ef96 --- /dev/null +++ b/test/sinon.test.mjs @@ -0,0 +1,182 @@ +import assert from '../lib/assert.mjs' +import t from '../lib/eltro.mjs' +import { spy, stub } from '../index.mjs' + +[spy, stub].forEach(function(tester, i) { + t.describe(`#${i === 0 ? 'spy' : 'stub'}()`, function() { + t.test('should keep track of call count', function() { + let spyer = tester() + assert.strictEqual(spyer.callCount, 0) + assert.notOk(spyer.called) + assert.strictEqual(spyer.firstCall, null) + assert.strictEqual(spyer.secondCall, null) + assert.strictEqual(spyer.thirdCall, null) + + spyer() + assert.strictEqual(spyer.callCount, 1) + assert.ok(spyer.called) + assert.deepStrictEqual(spyer.firstCall, []) + assert.strictEqual(spyer.secondCall, null) + assert.strictEqual(spyer.thirdCall, null) + + spyer() + assert.strictEqual(spyer.callCount, 2) + assert.ok(spyer.called) + assert.deepStrictEqual(spyer.firstCall, []) + assert.deepStrictEqual(spyer.secondCall, []) + assert.strictEqual(spyer.thirdCall, null) + + spyer() + assert.strictEqual(spyer.callCount, 3) + assert.ok(spyer.called) + assert.deepStrictEqual(spyer.firstCall, []) + assert.deepStrictEqual(spyer.secondCall, []) + assert.deepStrictEqual(spyer.thirdCall, []) + + spyer() + assert.strictEqual(spyer.callCount, 4) + }) + + t.test('should keep track of arguments used to 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]) + assert.strictEqual(spyer.callCount, 3) + + assert.strictEqual(spyer.onCall(0)[0], assertFirstArgs) + assert.strictEqual(spyer.onCall(1)[0], assertSecondArgs) + assert.strictEqual(spyer.onCall(2)[0], assertThirdArgs[0]) + assert.strictEqual(spyer.onCall(2)[1], assertThirdArgs[1]) + + assert.strictEqual(spyer.firstCall[0], assertFirstArgs) + assert.strictEqual(spyer.secondCall[0], assertSecondArgs) + assert.strictEqual(spyer.thirdCall[0], assertThirdArgs[0]) + assert.strictEqual(spyer.thirdCall[1], assertThirdArgs[1]) + }) + }) +}) + +t.describe('#stub()', function() { + t.test('should support returns', function() { + const assertInput = { a : 1 } + const assertReturns = { b: 3 } + let s = stub() + assert.strictEqual(s(), undefined) + s.returns(assertReturns) + assert.strictEqual(s(assertInput), assertReturns) + assert.ok(s.called) + assert.strictEqual(s.callCount, 2) + assert.strictEqual(s.secondCall[0], assertInput) + }) + + t.test('should support throws', function() { + const assertInput = { a : 1 } + const assertThrows = new Error('testety test') + let s = stub() + assert.strictEqual(s(), undefined) + s.throws(assertThrows) + assert.throws(function() { + s(assertInput) + }, assertThrows) + assert.ok(s.called) + assert.strictEqual(s.callCount, 2) + assert.strictEqual(s.secondCall[0], assertInput) + }) + + t.test('should support resolves', async function() { + const assertInput = { a : 1 } + const assertReturns = { b: 3 } + let s = stub() + assert.strictEqual(s(), undefined) + s.resolves(assertReturns) + let promiser = s(assertInput) + assert.ok(promiser.then) + assert.strictEqual(typeof(promiser.then), 'function') + let output = await promiser + assert.strictEqual(output, assertReturns) + }) + + t.test('should support rejects', async function() { + const assertInput = { a : 1 } + const assertReturns = { b: 3 } + let s = stub() + assert.strictEqual(s(), undefined) + s.rejects(assertReturns) + let promiser = s(assertInput) + assert.ok(promiser.then) + assert.strictEqual(typeof(promiser.then), 'function') + let output = await assert.isRejected(promiser) + assert.strictEqual(output, assertReturns) + }) + + t.test('should support custom function overwrite', async function() { + const assertInput = { a : 1 } + const assertReturns = { b: 3 } + let ourCallCount = 50 + let s = stub(function(input) { + ourCallCount++ + assert.strictEqual(input, assertInput) + return assertReturns + }) + assert.strictEqual(s(assertInput), assertReturns) + assert.ok(s.called) + assert.strictEqual(s.callCount, 1) + assert.strictEqual(ourCallCount, 51) + assert.strictEqual(s.firstCall[0], assertInput) + + assert.throws(function() { + s(null) + }) + }) + + t.test('should support custom function overwrite after the fact', async function() { + const assertInput = { a : 1 } + const assertReturns = { b: 3 } + let ourCallCount = 0 + let s = stub() + assert.strictEqual(s(null), undefined) + s.returnWith(function(input) { + ourCallCount++ + assert.strictEqual(input, assertInput) + return assertReturns + }) + assert.strictEqual(s(assertInput), assertReturns) + assert.ok(s.called) + assert.strictEqual(s.callCount, 2) + assert.strictEqual(ourCallCount, 1) + assert.strictEqual(s.secondCall[0], assertInput) + + assert.throws(function() { + s(null) + }) + }) + + t.test('should throw if parameter is something else than a function', function() { + assert.ok(stub(null)) + assert.ok(stub(0)) + assert.ok(stub('')) + assert.strictEqual(stub(null)(), undefined) + assert.strictEqual(stub(0)(), undefined) + assert.strictEqual(stub('')(), undefined) + assert.throws(function() { stub([]) }) + assert.throws(function() { stub({}) }) + assert.throws(function() { stub(123) }) + assert.throws(function() { stub('asdf') }) + }) + + t.test('should throw if returnWith is called with something else than a function', function() { + assert.throws(function() { stub().returnWith(0) }) + assert.throws(function() { stub().returnWith('') }) + assert.throws(function() { stub().returnWith(null) }) + assert.throws(function() { stub().returnWith([]) }) + assert.throws(function() { stub().returnWith({}) }) + assert.throws(function() { stub().returnWith(123) }) + assert.throws(function() { stub().returnWith('asdf') }) + }) +})