diff --git a/simple-mock/simple-mock-tests.ts b/simple-mock/simple-mock-tests.ts new file mode 100644 index 000000000..e83b22aff --- /dev/null +++ b/simple-mock/simple-mock-tests.ts @@ -0,0 +1,1024 @@ +/// +/// +/// + +/// + +'use strict' + +import simple = require('simple-mock'); +import assert = require('assert'); + +import Bluebird = require('bluebird'); + +// Following code is a TypeScript convertion of the test suite bundled with simple-mock. +// Original test in MIT license + +describe('simple', function () { + describe('spy()', function () { + describe('for noop function', function () { + let spyFn: Simple.Spy; + + beforeEach(function () { + spyFn = simple.spy(function () {}) + }) + + it('can be queried without having been called', function () { + assert.equal(spyFn.callCount, 0) + assert.deepEqual(spyFn.calls, []) + assert(spyFn.lastCall) + assert.deepEqual(spyFn.lastCall.args, []) + }) + + it('can be queried for arguments on a single call', function () { + let context = { + spyFn: spyFn + } + + context.spyFn('with', 'args') + + assert(spyFn.called) + assert.equal(spyFn.callCount, 1) + assert(spyFn.calls) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall, spyFn.lastCall) + assert.equal(spyFn.firstCall, spyFn.calls[0]) + assert.deepEqual(spyFn.lastCall.args, ['with', 'args']) + assert.equal(spyFn.lastCall.context, context) + }) + + it('can be queried for arguments over multiple calls', function () { + let context = { + spyFn: spyFn + } + + spyFn('with', 'args') + spyFn('and') + context.spyFn('more', 'args') + + assert(spyFn.called) + assert.equal(spyFn.callCount, 3) + assert(spyFn.calls) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall, spyFn.calls[0]) + assert.deepEqual(spyFn.firstCall.args, ['with', 'args']) + assert(spyFn.calls[1]) + assert.deepEqual(spyFn.calls[1].args, ['and']) + assert(spyFn.lastCall) + assert.equal(spyFn.lastCall, spyFn.calls[2]) + assert.deepEqual(spyFn.lastCall.args, ['more', 'args']) + assert.equal(spyFn.lastCall.context, context) + }) + }) + + describe('for a throwing function', function () { + let originalFn: () => void; + let spyFn: Simple.Spy; + beforeEach(function () { + let i = 0 + + originalFn = function () { + throw new Error(`${i++}`) + } + + spyFn = simple.spy(originalFn) + }) + + it('can be queried without having been called', function () { + assert(!spyFn.called) + assert.equal(spyFn.callCount, 0) + assert.deepEqual(spyFn.calls, []) + assert(spyFn.lastCall) + assert.equal(spyFn.lastCall.threw, undefined) + }) + + it('can be queried for what it threw on a single call', function () { + let threw: Error; + try { + spyFn() + } catch (e) { + threw = e + } + + assert(threw) + assert(spyFn.called) + assert.equal(spyFn.callCount, 1) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall.threw, threw) + }) + + it('can be queried for what it threw over multiple calls', function () { + let threw: Error[] = [] + try { + spyFn() + } catch (e) { + threw.push(e) + } + try { + spyFn() + } catch (e) { + threw.push(e) + } + try { + spyFn() + } catch (e) { + threw.push(e) + } + + assert.equal(threw.length, 3) + assert(spyFn.called) + assert.equal(spyFn.callCount, 3) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall.threw, threw[0]) + assert.equal(spyFn.calls[1].threw, threw[1]) + assert.equal(spyFn.lastCall.threw, threw[2]) + }) + }) + + describe('for a returning function', function () { + let originalFn: () => number; + let spyFn: Simple.Spy; + beforeEach(function () { + let i = 1 + + originalFn = () => { + return i++ + } + + spyFn = simple.spy(originalFn) + }) + + it('can be queried without having been called', function () { + assert(!spyFn.called) + assert.equal(spyFn.callCount, 0) + assert.deepEqual(spyFn.calls, []) + assert(spyFn.lastCall) + assert.equal(spyFn.lastCall.returned, undefined) + }) + + it('can be queried for what it threw on a single call', function () { + let returned: number + + returned = spyFn() + + assert(returned) + assert.equal(spyFn.callCount, 1) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall.returned, returned) + }) + + it('can be queried for what it threw over multiple calls', function () { + let returned: number[] = [] + + returned.push(spyFn()) + returned.push(spyFn()) + returned.push(spyFn()) + + assert.equal(returned.length, 3) + assert(spyFn.called) + assert.equal(spyFn.callCount, 3) + assert(spyFn.firstCall) + assert.equal(spyFn.firstCall.returned, returned[0]) + assert.equal(spyFn.calls[1].returned, returned[1]) + assert.equal(spyFn.lastCall.returned, returned[2]) + }) + }) + + describe('calls of multiple spies', function () { + it('can be compared to determine the order they were called in', function () { + let spy1 = simple.spy(function () {}) + let spy2 = simple.spy(function () {}) + let spy3 = simple.spy(function () {}) + + spy1() + spy3() + spy2() + spy1() + + assert(spy1.lastCall.k > spy2.lastCall.k) + assert(spy1.lastCall.k > spy3.lastCall.k) + assert(spy2.lastCall.k > spy3.lastCall.k) + assert(spy3.lastCall.k > spy1.calls[0].k) + }) + }) + }) + + describe('stub()', function () { + describe('with no configuration', function () { + let stubFn: Simple.Stub; + it('is also a spy', function () { + stubFn = simple.stub() + + stubFn('etc') + assert(stubFn.called) + assert(stubFn.lastCall.args[0], 'etc') + }) + }) + + describe('for a single callback configuration', function () { + let stubFn: Simple.Stub; + describe('with default index', function () { + beforeEach(function () { + stubFn = simple.stub().callbackWith(1, 2, 3) + }) + + it('can call back with arguments', function () { + stubFn('a', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 1) + assert.equal(stubFn.lastCall.args[0], 'a') + assert.equal(arguments.length, 3) + assert.equal(arguments[0], 1) + assert.equal(arguments[1], 2) + assert.equal(arguments[2], 3) + }) + }) + + it('can call back with arguments, over multiple calls', function () { + stubFn('a', function () {}) + stubFn('b', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 2) + assert.equal(stubFn.lastCall.args[0], 'b') + assert.equal(arguments.length, 3) + assert.equal(arguments[0], 1) + assert.equal(arguments[1], 2) + assert.equal(arguments[2], 3) + }) + }) + }) + + describe('with specified index', function () { + beforeEach(function () { + stubFn = simple.stub().callbackArgWith(1, 2, 3) + }) + + it('can call back with arguments', function () { + stubFn('a', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 1) + assert.equal(stubFn.lastCall.args[0], 'a') + assert.equal(arguments.length, 2) + assert.equal(arguments[0], 2) + assert.equal(arguments[1], 3) + }) + }) + + it('can call back with arguments, over multiple calls', function () { + stubFn('a', function () {}) + stubFn('b', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 2) + assert.equal(stubFn.lastCall.args[0], 'b') + assert.equal(arguments.length, 2) + assert.equal(arguments[0], 2) + assert.equal(arguments[1], 3) + }) + }) + }) + + describe('with context specified', function () { + beforeEach(function () { + stubFn = simple.stub().callback().inThisContext({ a: 'a' }) + }) + + it('should do what...', function (done) { + stubFn(function () { + assert.equal(this.a, 'a') + done() + }) + }) + }) + }) + + describe('for a multiple callback configurations', function () { + let stubFn: Simple.Stub; + beforeEach(function () { + stubFn = simple.stub().callbackWith(1).callbackWith(2).callbackWith(3) + }) + + it('can call back once with arguments', function () { + stubFn('a', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 1) + assert.equal(stubFn.lastCall.args[0], 'a') + assert.equal(arguments[0], 1) + }) + }) + + it('can call back with arguments, over multiple calls, looping per default', function () { + stubFn('a', function () {}) + stubFn('b', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 2) + assert.equal(stubFn.lastCall.args[0], 'b') + assert.equal(arguments[0], 2) + }) + stubFn('c', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(stubFn.lastCall.args[0], 'c') + assert.equal(arguments[0], 3) + }) + stubFn('d', function () { + assert.equal(stubFn.callCount, 4) + assert.equal(stubFn.lastCall.args[0], 'd') + assert.equal(arguments[0], 1) + }) + }) + + it('can call back with arguments, over multiple calls, looping turned off', function () { + stubFn.loop = false + stubFn('a', function () {}) + stubFn('b', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 2) + assert.equal(stubFn.lastCall.args[0], 'b') + assert.equal(arguments[0], 2) + }) + stubFn('c', function () { + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(stubFn.lastCall.args[0], 'c') + assert.equal(arguments[0], 3) + }) + let neverCalled = true + stubFn('d', function () { + neverCalled = false + }) + assert(neverCalled) + }) + }) + + describe('for a single throwing configuration', function () { + let stubFn: Simple.Stub; + beforeEach(function () { + stubFn = simple.stub().throwWith(new Error('example')) + }) + + it('can throw', function () { + let threw: Error + try { + stubFn() + } catch (e) { + threw = e + } + + assert(threw) + assert(stubFn.called) + assert.equal(stubFn.callCount, 1) + assert.equal(threw.message, 'example') + }) + + it('can throw over multiple calls, looping per default', function () { + let threw: Error[] = [] + try { + stubFn() + } catch (e) { + threw.push(e) + } + try { + stubFn() + } catch (e) { + threw.push(e) + } + + assert.equal(threw.length, 2) + assert(stubFn.called) + assert.equal(stubFn.callCount, 2) + assert.equal(threw[0], threw[1]) + assert.equal(threw[0].message, 'example') + }) + }) + + describe('for a multiple throwing configurations', function () { + let stubFn: Simple.Stub; + beforeEach(function () { + stubFn = simple.stub().throwWith(new Error('a')).throwWith(new Error('b')) + }) + + it('can throw', function () { + let threw: Error + try { + stubFn() + } catch (e) { + threw = e + } + + assert(threw) + assert(stubFn.called) + assert.equal(stubFn.callCount, 1) + assert.equal(threw.message, 'a') + }) + + it('can throw over multiple calls, looping per default', function () { + let threw: Error[] = [] + try { + stubFn() + } catch (e) { + threw.push(e) + } + try { + stubFn() + } catch (e) { + threw.push(e) + } + try { + stubFn() + } catch (e) { + threw.push(e) + } + + assert.equal(threw.length, 3) + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(threw[0].message, 'a') + assert.equal(threw[1].message, 'b') + assert.equal(threw[2].message, 'a') + }) + + it('can throw over multiple calls, looping turned off', function () { + stubFn.loop = false + + let threw: Error[] = [] + try { + stubFn() + } catch (e) { + threw.push(e) + } + try { + stubFn() + } catch (e) { + threw.push(e) + } + try { + stubFn() + } catch (e) { + threw.push(e) + } + + assert.equal(threw.length, 2) + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(threw[0].message, 'a') + assert.equal(threw[1].message, 'b') + }) + }) + + describe('for a single returning configuration', function () { + let stubFn: Simple.Stub; + beforeEach(function () { + stubFn = simple.stub() + }) + + it('can return', function () { + stubFn.returnWith('example') + + let returned: string + returned = stubFn() + + assert(returned) + assert.equal(stubFn.callCount, 1) + assert.equal(returned, 'example') + }) + + it('can return an empty string', function () { + stubFn.returnWith('') + + let returned: string + returned = stubFn() + + assert.equal(stubFn.callCount, 1) + assert.equal(returned, '') + }) + + it('can return over multiple calls, looping per default', function () { + stubFn.returnWith('example-a') + stubFn.returnWith('example-b') + + let returned: string[] = [] + returned.push(stubFn()) + returned.push(stubFn()) + returned.push(stubFn()) + returned.push(stubFn()) + + assert.equal(returned.length, 4) + assert(stubFn.called) + assert.equal(stubFn.callCount, 4) + assert.equal(returned[0], returned[2]) + assert.equal(returned[0], 'example-a') + assert.equal(returned[1], returned[3]) + assert.equal(returned[1], 'example-b') + }) + }) + + describe('for a multiple returning configurations', function () { + let stubFn: Simple.Stub; + beforeEach(function () { + stubFn = simple.stub().returnWith('a').returnWith('b') + }) + + it('can return', function () { + let returned: string + returned = stubFn() + + assert(returned) + assert.equal(stubFn.callCount, 1) + assert.equal(returned, 'a') + }) + + it('can return over multiple calls, looping per default', function () { + let returned: string[] = [] + returned.push(stubFn()) + returned.push(stubFn()) + returned.push(stubFn()) + + assert.equal(returned.length, 3) + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(returned[0], 'a') + assert.equal(returned[1], 'b') + assert.equal(returned[2], 'a') + }) + + it('can return over multiple calls, looping turned off', function () { + stubFn.loop = false + + let returned: string[] = [] + returned.push(stubFn()) + returned.push(stubFn()) + returned.push(stubFn()) + + assert.equal(returned.length, 3) + assert(stubFn.called) + assert.equal(stubFn.callCount, 3) + assert.equal(returned[0], 'a') + assert.equal(returned[1], 'b') + assert.equal(returned[2], undefined) + }) + }) + + describe('for a specified function to call', function () { + it('should be called with arguments and return', function () { + let stubFn = simple.stub().callFn(function () { + return arguments + }) + + let returned = stubFn('z', 'x') + + assert.equal(stubFn.callCount, 1) + assert.equal(returned[0], 'z') + assert.equal(returned[1], 'x') + }) + + it('should be able to throw', function () { + let stubFn = simple.stub().callFn(function () { + throw new Error('my message') + }) + + try { + stubFn() + } catch(e) { + assert(e instanceof Error) + assert.equal(e.message, 'my message') + } + }) + + it('should be called in context', function () { + let mockObj = { + stubFn: simple.stub().callFn(function () { + return this + }) + } + + let returned = mockObj.stubFn() + + assert.equal(returned, mockObj) + }) + + it('can be called in specified context', function () { + let anotherMockObj = {} + + let mockObj = { + stubFn: simple.stub().callFn(function () { + return this + }).inThisContext(anotherMockObj) + } + + let returned = mockObj.stubFn() + + assert.equal(returned, anotherMockObj) + }) + }) + + describe('for custom/when-conforming promises', function () { + let fulfilledStub: Simple.Stub + let rejectedStub: Simple.Stub + + beforeEach(function () { + fulfilledStub = simple.stub().returnWith(true) + rejectedStub = simple.stub().returnWith(true) + + interface MockPromise { + resolveValue: T, + rejectValue: T, + then(fulfilledFn: (value: any) => T, rejectedFn: (error: any) => T): void; + } + + let mockPromise: MockPromise = { + resolveValue: null as boolean, + rejectValue: null as boolean, + then: function (fulfilledFn: (value: any) => boolean, rejectedFn: (error: any) => boolean) { + let self = this + process.nextTick(function () { + if (self.resolveValue) return fulfilledFn(self.resolveValue) + if (self.rejectValue) return rejectedFn(self.rejectValue) + }) + } + } + + simple.mock(simple, 'Promise', { + when: function(value: T) { + let promise: MockPromise = Object.create(mockPromise) + promise.resolveValue = value + return promise + }, + reject: function(value: T) { + let promise: MockPromise = Object.create(mockPromise) + promise.rejectValue = value + return promise + } + }) + }) + + describe('with a single resolving configuration', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().resolveWith('example') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 1) + assert.equal(fulfilledStub.lastCall.arg, 'example') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + }) + + describe('with a multiple resolving configurations', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().resolveWith('a').resolveWith('b') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 1) + assert.equal(fulfilledStub.lastCall.arg, 'a') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + + it('can return over multiple calls, looping per default', function (done) { + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 3) + assert.equal(fulfilledStub.calls[0].arg, 'a') + assert.equal(fulfilledStub.calls[1].arg, 'b') + assert.equal(fulfilledStub.calls[2].arg, 'a') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + }) + + describe('with a single rejecting configuration', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().rejectWith('example') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 1) + assert.equal(rejectedStub.lastCall.arg, 'example') + done() + }, 0) + }) + }) + + describe('with a multiple rejecting configurations', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().rejectWith('a').rejectWith('b') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 1) + assert.equal(rejectedStub.lastCall.arg, 'a') + done() + }, 0) + }) + + it('can return over multiple calls, looping per default', function (done) { + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 3) + assert.equal(rejectedStub.calls[0].arg, 'a') + assert.equal(rejectedStub.calls[1].arg, 'b') + assert.equal(rejectedStub.calls[2].arg, 'a') + done() + }, 0) + }) + }) + }) + + describe('for native/conforming promises', function () { + let fulfilledStub: Simple.Stub + let rejectedStub: Simple.Stub + + beforeEach(function () { + fulfilledStub = simple.stub().returnWith(true) + rejectedStub = simple.stub().returnWith(true) + }) + + describe('with a single resolving configuration', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().resolveWith('example') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 1) + assert.equal(fulfilledStub.lastCall.arg, 'example') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + }) + + describe('with a multiple resolving configurations', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().resolveWith('a').resolveWith('b') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 1) + assert.equal(fulfilledStub.lastCall.arg, 'a') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + + it('can return over multiple calls, looping per default', function (done) { + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 3) + assert.equal(fulfilledStub.calls[0].arg, 'a') + assert.equal(fulfilledStub.calls[1].arg, 'b') + assert.equal(fulfilledStub.calls[2].arg, 'a') + assert.equal(rejectedStub.callCount, 0) + done() + }, 0) + }) + }) + + describe('with a single rejecting configuration', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().rejectWith('example') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 1) + assert.equal(rejectedStub.lastCall.arg, 'example') + done() + }, 0) + }) + }) + + describe('with a multiple rejecting configurations', function () { + let stubFn: Simple.Stub>; + beforeEach(function () { + stubFn = simple.stub().rejectWith('a').rejectWith('b') + }) + + it('can return a promise', function (done) { + let returned = stubFn() + + assert(returned) + + returned.then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 1) + assert.equal(rejectedStub.lastCall.arg, 'a') + done() + }, 0) + }) + + it('can return over multiple calls, looping per default', function (done) { + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + stubFn().then(fulfilledStub, rejectedStub) + + setTimeout(function () { + assert.equal(fulfilledStub.callCount, 0) + assert.equal(rejectedStub.callCount, 3) + assert.equal(rejectedStub.calls[0].arg, 'a') + assert.equal(rejectedStub.calls[1].arg, 'b') + assert.equal(rejectedStub.calls[2].arg, 'a') + done() + }, 0) + }) + }) + }) + }) + + describe('mock()', function () { + describe('on a object with prototype', function () { + class ProtoKlass { + protoValue: string = 'x' + protoFn() { + return 'x' + } + } + + let obj: any + + before(function () { + }) + + beforeEach(function () { + obj = new ProtoKlass() + }) + + it('can mock instance values over its prototype\'s and restore', function () { + simple.mock(obj, 'protoValue', 'y') + assert.equal(obj.protoValue, 'y') + simple.restore() + assert.equal(obj.protoValue, 'x') + }) + + it('can mock with custom instance functions over its prototype\'s and restore', function () { + simple.mock(obj, 'protoFn', function () { + return 'y' + }) + assert.equal(obj.protoFn(), 'y') + assert(obj.protoFn.called) + simple.restore() + assert.equal(obj.protoFn(), 'x') + }) + + it('can mock with stubbed functions over its prototype\'s and restore', function () { + simple.mock(obj, 'protoFn').returnWith('y') + assert.equal(obj.protoFn(), 'y') + assert(obj.protoFn.called) + simple.restore() + assert.equal(obj.protoFn(), 'x') + }) + + it('can mock with stubbed functions and prototype\'s original over its prototype\'s and restore', function () { + simple.mock(obj, 'protoFn').returnWith('y').callOriginal().returnWith('z') + assert.equal(obj.protoFn(), 'y') + assert.equal(obj.protoFn(), 'x') + assert.equal(obj.protoFn(), 'z') + assert.equal(obj.protoFn.callCount, 3) + simple.restore() + assert.equal(obj.protoFn(), 'x') + }) + }) + + describe('on an anonymous object', function () { + let obj: any + beforeEach(function () { + obj = { + a: 'a', + b: 'b', + c: 'c', + fnD: function () { + return 'd' + } + } + }) + + it('can mock instance values and restore', function () { + let beforeKeys = Object.keys(obj) + simple.mock(obj, 'a', 'd') + simple.mock(obj, 'd', 'a') + assert.equal(obj.a, 'd') + assert.equal(obj.d, 'a') + simple.restore() + assert.equal(obj.a, 'a') + assert.equal(obj.d, undefined) + assert.deepEqual(Object.keys(obj), beforeKeys) + }) + + it('can mock with spy on pre-existing functions and restore', function () { + simple.mock(obj, 'fnD').returnWith('a') + assert.equal(obj.fnD(), 'a') + assert(obj.fnD.called) + simple.restore() + assert.equal(obj.fnD(), 'd') + }) + + it('can mock with newly stubbed functions and restore', function () { + simple.mock(obj, 'fnA').returnWith('a') + assert.equal(obj.fnA(), 'a') + assert(obj.fnA.called) + simple.restore() + assert.equal(obj.fnA, undefined) + }) + }) + + describe('with one argument', function () { + it('returns a spy', function () { + let called = 0 + + let spy = simple.mock(function () { + called++ + }) + + spy() + assert.equal(called, 1) + assert(spy.called) + }) + }) + + describe('with no arguments', function () { + it('returns a stub', function () { + let stub = simple.mock().returnWith('x') + + let x = stub() + assert(stub.called) + assert(x, 'x') + }) + }) + }) +}) + +simple.Promise = Bluebird; diff --git a/simple-mock/simple-mock.d.ts b/simple-mock/simple-mock.d.ts new file mode 100644 index 000000000..760ef67b4 --- /dev/null +++ b/simple-mock/simple-mock.d.ts @@ -0,0 +1,194 @@ +// Type definitions for simple-mock +// Project: https://github.com/jupiter/simple-mock +// Definitions by: Leon Yu +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare namespace Simple { + type Fn = { + (...args: any[]): T + } + + export interface Static { + /** + * Restores all current mocks. + */ + restore(): void; + + /** + * Wraps fn in a spy and sets this on the obj, restorable with all mocks. + */ + mock(obj: any, key: string, fn: Fn): Stub; + + /** + * Sets the value on this object. E.g. mock(config, 'title', 'test') is the same as config.title = 'test', but restorable with all mocks. + */ + mock(obj: any, key: string, mockValue: T): T; + + /** + * If obj has already has this function, it is wrapped in a spy. The resulting spy can be turned into a stub by further configuration. Restores with all mocks. + */ + mock(obj: any, key: string): Stub; + mock(obj: any, key: string): Stub; + + /** + * Wraps fn in a spy. + */ + spy(fn: Fn): Spy; + /** + * Wraps fn in a spy. + */ + mock(fn: Fn): Spy; + + /** + * Returns a stub function that is also a spy. + */ + stub(): Stub; + stub(): Stub; + + /** + * Returns a stub function that is also a spy. + */ + mock(): Stub; + mock(): Stub; + + Promise?: PromiseConstructorLike; + } + + interface Calls { + /** + * an array of arguments received on the call + */ + args: any[]; + /** + * first argument + */ + arg: any; + /** + * the context (this) of the call + */ + context: any; + /** + * the value returned by the wrapped function + */ + returned: T; + /** + * the error thrown by the wrapped function + */ + threw: Error; + /** + * autoincrementing number, can be compared to evaluate call order + */ + k: number; + } + + export interface Spy{ + (...args: any[]): T; + + called: boolean; + /** + * Number of times the function was called. + */ + callCount: number; + calls: Calls[]; + firstCall: Calls; + /** + * The last call object. (This is often also the first and only call.) + */ + lastCall: Calls; + /** + * Resets all counts and properties to the original state. + */ + reset(): void; + } + + interface Action { + /** + * arguments to call back with + */ + cbArgs: ArrayLike; + returnValue: T; + throwError: Error; + } + + export interface Stub extends Spy { + /** + * Configures this stub to call this function, returning its return value. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callFn(fn: Fn): Stub; + + /** + * Configures this stub to call the original, unstubbed function, returning its return value. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callOriginal(): Stub; + + /** + * Configures this stub to return with this value. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + returnWith(val: R): Stub; + + /** + * Configures this stub to throw this error. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + throwWith(err: Error): Stub; + + /** + * Configures this stub to call back with the arguments passed. It will use either the last argument as callback, or the argument at cbArgumentIndex. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callback(...args: any[]): Stub; + /** + * Configures this stub to call back with the arguments passed. It will use either the last argument as callback, or the argument at cbArgumentIndex. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callbackWith(...args: any[]): Stub; + + /** + * Configures this stub to call back with the arguments passed. It will use either the last argument as callback, or the argument at cbArgumentIndex. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callbackAtIndex(cbArgumentIndex: number, ...args: any[]): Stub; + /** + * Configures this stub to call back with the arguments passed. It will use either the last argument as callback, or the argument at cbArgumentIndex. + * Subsequent calls of this on the same stub (chainable) will queue up different behaviours for each subsequent call of the stub. + */ + callbackArgWith(cbArgumentIndex: number, ...args: any[]): Stub; + + + /** + * Configures the last configured function or callback to be called in this context, i.e. this will be obj. + */ + inThisContext(obj: any): Stub; + + /** + * Configures the stub to return a Promise (where available] resolving to this value. Same as stub.returnWith(Promise.resolve(val)). + * You can use a custom Promise-conforming library, i.e. simple.Promise = require('bluebird') or simple.Promise = $q. + */ + resolveWith(val: V): Stub>; + + /** + * Configures the stub to return a Promise (where available) rejecting with this error. Same as stub.returnWith(Promise.reject(val)). + * You can use a custom Promise-conforming library, i.e. simple.Promise = require('bluebird') or simple.Promise = $q. + */ + rejectWith(val: V): Stub>; + + /** + * An array of behaviours, each having one of these properties: + */ + actions: Action[]; + + /** + * setting whether the queue of actions for this stub should repeat. + * @default true + */ + loop: boolean; + } +} + +declare module "simple-mock" { + var simple: Simple.Static; + export = simple; +}