UNPKG

advisable

Version:

Functional mixin for sync and async before/after/around advice

1,680 lines (1,196 loc) 56.6 kB
/** * unit.mocha.js * * Unit tests with stubbing at every step in the call chain to assert * expectations on both function call arguments and return values. */ var path = require('path') , sinon = require('sinon') , advisable = require(path.resolve(__dirname, '..', 'lib', 'advisable')); describe('advisable:unit', function () { beforeEach(function () { this.target = sinon.stub(); this.obj = { target: this.target }; }); describe('sync', function () { beforeEach(function () { advisable.sync.call(this.obj); }); describe('when advisable methods are mixed in but not used', function () { it('calls the target function with the passed arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly(1, 2, 3); }); it('calls the target function with the correct context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target function throws', function () { beforeEach(function () { this.error = new Error('target-throws'); this.target.throws(this.error); }); it('throws the errors', function () { this.obj.target.should.throw(this.error); }); }); describe('when the target function returns', function () { beforeEach(function () { this.retval = 'target-return'; this.target.returns(this.retval); }); it('returns the target function return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); describe('.beforeSync', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.before = sinon.stub(); this.obj.beforeSync('target', this.before, { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3); this.before.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice throws', function () { beforeEach(function () { this.error = 'beforeSync-mutate-before-throws'; this.before.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the target method', function () { try { this.obj.target(); } catch (e) {} this.target.should.not.have.been.called; }); }); describe('when the before advice returns', function () { beforeEach(function () { this.retval = [ 'beforeSync-mutate-before-return-one' , 'beforeSync-mutate-before-return-two' ]; this.before.returns(this.retval); }); it('invokes the target with the returned arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly( this.retval[0] , this.retval[1] ); }); it('invokes the target in the target object context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = new Error('beforeSync-mutate-target-throws'); this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'beforeSync-mutate-target-return'; this.target.returns(this.retval); }); it('returns the target method return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.before = sinon.stub(); this.obj.beforeSync('target', this.before); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3); this.before.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice throws', function () { beforeEach(function () { this.error = 'beforeSync-before-throws'; this.before.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the target method', function () { try { this.obj.target(); } catch (e) {} this.target.should.not.have.been.called; }); }); describe('when the before advice returns', function () { beforeEach(function () { this.retval = [ 'beforeSync-before-return-one' , 'beforeSync-before-return-two' ]; this.before.returns(this.retval); }); it('invokes the target with the original arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes the target in the target object context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = new Error('beforeSync-target-throws'); this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'beforeSync-target-return'; this.target.returns(this.retval); }); it('returns the target method return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); }); describe('.afterSync', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.after = sinon.stub(); this.obj.afterSync('target', this.after, { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes the target with the passed arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes the target in the target object context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = 'afterSync-mutate-target-throws'; this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the after advice', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'afterSync-mutate-target-return'; this.target.returns(this.retval); }); it('invokes the after advice with the return value', function () { this.obj.target(1, 2, 3); this.after.should.have.been.calledWithExactly(this.retval); }); it('invokes the advice in the target object context', function () { this.obj.target(1, 2, 3); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the advising method throws', function () { beforeEach(function () { this.error = new Error('afterSync-mutate-after-throws'); this.after.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the advising method returns', function () { beforeEach(function () { this.retval = 'afterSync-mutate-target-return'; this.after.returns(this.retval); }); it('returns the advising method return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.after = sinon.stub(); this.obj.afterSync('target', this.after); }); describe('when the target method is invoked', function () { it('invokes the target with the passed arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes the target in the target object context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = 'afterSync-mutate-target-throws'; this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the advising method', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'afterSync-target-return'; this.target.returns(this.retval); }); it('invokes the advice with the original arguments', function () { this.obj.target(1, 2, 3); this.after.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes the advice in the target object context', function () { this.obj.target(1, 2, 3); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the advising method throws', function () { beforeEach(function () { this.error = new Error('afterSync-after-throws'); this.after.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the advising method returns', function () { beforeEach(function () { this.afterRetval = 'afterSync-after-return'; this.after.returns(this.afterRetval); }); it('returns the advising method return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); }); describe('.aroundSync', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.before = sinon.stub(); this.after = sinon.stub(); this.obj.aroundSync( 'target' , this.before , this.after , { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3); this.before.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice throws', function () { beforeEach(function () { this.error = 'aroundSync-mutate-before-throws'; this.before.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the target method', function () { try { this.obj.target(); } catch (e) {} this.target.should.not.have.been.called; }); it('does not call the after advice', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the before advice returns', function () { beforeEach(function () { this.retval = [ 'aroundSync-mutate-before-return-one' , 'aroundSync-mutate-before-return-two' ]; this.before.returns(this.retval); }); it('invokes the target with the returned arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly( this.retval[0] , this.retval[1] ); }); it('invokes the target in the target context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = new Error('aroundSync-mutate-target-throws'); this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the after advice', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'aroundSync-mutate-target-return'; this.target.returns(this.retval); }); it('invokes after advice with target return value', function () { this.obj.target(1, 2, 3); this.after.should.have.been.calledWithExactly(this.retval); }); it('invokes the advice in the target context', function () { this.obj.target(1, 2, 3); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the after advice throws', function () { beforeEach(function () { this.error = new Error('aroundSync-mutate-after-throws'); this.after.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the after advice returns', function () { beforeEach(function () { this.retval = 'aroundSync-mutate-after-return'; this.after.returns(this.retval); }); it('returns the after advice return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.before = sinon.stub(); this.after = sinon.stub(); this.obj.aroundSync('target', this.before, this.after); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3); this.before.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice throws', function () { beforeEach(function () { this.error = 'aroundSync-before-throws'; this.before.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the target method', function () { try { this.obj.target(); } catch (e) {} this.target.should.not.have.been.called; }); it('does not call the after advice', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the before advice returns', function () { beforeEach(function () { this.retval = [ 'aroundSync-before-return-one' , 'aroundSync-before-return-two' ]; this.before.returns(this.retval); }); it('invokes the target with the original arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes the target in the target context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method throws', function () { beforeEach(function () { this.error = new Error('aroundSync-target-throws'); this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the after advice', function () { try { this.obj.target(); } catch (e) {} this.after.should.not.have.been.called; }); }); describe('when the target method returns', function () { beforeEach(function () { this.retval = 'aroundSync-target-return'; this.target.returns(this.retval); }); it('invokes after advice with original arguments', function () { this.obj.target(1, 2, 3); this.after.should.have.been.calledWithExactly(1, 2, 3); }); it('invokes after advice in the target context', function () { this.obj.target(1, 2, 3); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the after advice throws', function () { beforeEach(function () { this.error = new Error('aroundSync-after-throws'); this.after.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); describe('when the after advice returns', function () { beforeEach(function () { this.afterRetval = 'aroundSync-after-return'; this.after.returns(this.retval); }); it('returns the target method return value', function () { this.obj.target(1, 2, 3).should.equal(this.retval); }); }); }); }); }); }); }); describe('.wrapSync', function () { beforeEach(function () { var self = this; this.wa = 'wrapper-modified-arg-a'; this.wb = 'wrapper-modified-arg-b'; this.wc = 'wrapper-modified-arg-c'; this.wrv = 'wrapper-modified-return-value'; this.rv = null; this.wrapper = function (wrapped, a, b, c) { self.rv = wrapped(self.wa, self.wb, self.wc); return self.wrv; }; sinon.spy(this, 'wrapper'); this.obj.wrapSync('target', this.wrapper); }); describe('when the target method is invoked', function () { it('invokes the wrapper with the target as first arg', function () { // Stub out auto-binding sinon.stub(this.target, 'bind').returns(this.target); this.obj.target(1, 2, 3); this.wrapper.should.have.been.calledWithExactly( this.target , 1 , 2 , 3 ); }); it('invokes the wrapper in the target context', function () { this.obj.target(1, 2, 3); this.wrapper.lastCall.thisValue.should.equal(this.obj); }); describe('when the wrapper invokes the target', function () { it('can modify the arguments', function () { this.obj.target(1, 2, 3); this.target.should.have.been.calledWithExactly( this.wa , this.wb , this.wc ); }); it('invokes in the target context', function () { this.obj.target(1, 2, 3); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the wrapped target returns', function () { beforeEach(function () { this.retval = 'wrapSync-target-return'; this.target.returns(this.retval); }); it('supplies a return value to the wrapper', function () { this.obj.target(1, 2, 3); this.rv.should.equal(this.retval); }); }); describe('when the wrapper returns', function () { it('supplies the wrapper return value', function () { this.obj.target(1, 2, 3).should.equal(this.wrv); }); }); }); describe('when the wrapper throws', function () { beforeEach(function () { this.error = 'wrapSync-throws'; }); describe('before calling the target', function () { beforeEach(function () { var self = this; this.obj.wrapSync('target', function (wrapped, a, b, c) { sinon.stub().throws(self.error)(); return wrapped(a, b, c); }); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); it('does not call the target', function () { try { this.obj.target(); } catch (e) {} this.target.should.not.have.been.called; }); }); describe('after calling the target', function () { beforeEach(function () { var self = this; this.obj.wrapSync('target', function (wrapped, a, b, c) { var rv = wrapped(a, b, c); sinon.stub().throws(self.error)(); return rv; }); }); it('calls the target', function () { try { this.obj.target(1, 2, 3); } catch (e) {} this.target.should.have.been.calledWithExactly( this.wa , this.wb , this.wc ); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); }); describe('when the target throws', function () { beforeEach(function () { this.error = 'wrapSync-target-throws'; this.target.throws(this.error); }); it('throws the error', function () { this.obj.target.should.throw(this.error); }); }); }); }); }); describe('async', function () { beforeEach(function () { advisable.async.call(this.obj); }); describe('when advisable methods are mixed in but not used', function () { describe('when the target function is invoked', function () { it('calls the target function with the passed arguments', function () { var callback = function () {}; this.obj.target(1, 2, 3, callback); this.target.should.have.been.calledWithExactly(1, 2, 3, callback); }); it('calls the target function in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target function errors', function () { beforeEach(function () { this.error = new Error('target-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the target function succeeds', function () { beforeEach(function () { this.retval = 'target-result'; this.target.yields(null, this.retval); }); it('calls back with the results', function (done) { this.obj.target(1, 2, 3, function (err, result) { result.should.equal(this.retval); done(); }.bind(this)); }); }); }); }); describe('.before', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.before = sinon.stub(); this.obj.before('target', this.before, { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.before.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice errors', function () { beforeEach(function () { this.error = new Error('before-mutate-before-errors'); this.before.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the target method', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.target.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the before advice succeeds', function () { beforeEach(function () { this.resultOne = 'before-mutate-before-result-one'; this.resultTwo = 'before-mutate-before-result-two'; this.resultThree = 'before-mutate-before-result-three'; this.before.yields( null , this.resultOne , this.resultTwo , this.resultThree ); }); it('invokes the target with the advice results', function () { this.obj.target(1, 2, 3, function () {}); this.target.should.have.been.calledWithExactly( this.resultOne , this.resultTwo , this.resultThree , sinon.match.func ); }); it('invokes the target in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method errors', function () { beforeEach(function () { this.error = new Error('before-mutate-target-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the target method succeeds', function () { beforeEach(function () { this.result = 'before-mutate-target-result'; this.target.yields(null, this.result); }); it('calls back with the target method results', function (done) { this.obj.target(1, 2, 3, function (err, result) { result.should.equal(this.result); done(); }.bind(this)); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.before = sinon.stub(); this.obj.before('target', this.before); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.before.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice errors', function () { beforeEach(function () { this.error = 'before-before-errors'; this.before.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the before advice succeeds', function () { beforeEach(function () { this.resultOne = 'before-before-result-one'; this.resultTwo = 'before-before-result-two'; this.resultThree = 'before-before-result-three'; this.before.yields( null , this.resultOne , this.resultTwo , this.resultThree ); }); it('invokes the target with the original arguments', function () { this.obj.target(1, 2, 3, function () {}); this.target.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes the target in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method errors', function () { beforeEach(function () { this.error = new Error('before-target-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the target method succeeds', function () { beforeEach(function () { this.result = 'before-target-result'; this.target.yields(null, this.result); }); it('calls back with the target method results', function (done) { this.obj.target(1, 2, 3, function (err, result) { result.should.equal(this.result); done(); }.bind(this)); }); }); }); }); }); }); describe('.after', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.after = sinon.stub(); this.obj.after('target', this.after, { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes the target with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.target.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes the target method in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method errors', function () { beforeEach(function () { this.error = 'after-mutate-target-errors'; this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the after advice', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.after.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the target method succeeds', function () { beforeEach(function () { this.result = 'after-mutate-target-result'; this.target.yields(null, this.result); }); it('invokes after advice with the target results', function () { this.obj.target(1, 2, 3, function () {}); this.after.should.have.been.calledWithExactly( this.result , sinon.match.func ); }); it('invokes after advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the advising method errors', function () { beforeEach(function () { this.error = new Error('after-mutate-after-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the advising method succeeds', function () { beforeEach(function () { this.afterResult = 'after-mutate-after-result'; this.after.yields(null, this.afterResult); }); it('calls back with the the advice results', function (done) { this.obj.target(1, 2, 3, function (err, result) { result.should.equal(this.afterResult); done(); }.bind(this)); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.after = sinon.stub(); this.obj.after('target', this.after); }); describe('when the target method is invoked', function () { it('invokes with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.target.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes the target method in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method errors', function () { beforeEach(function () { this.error = 'after-target-errors'; this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the after advice', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.after.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the target method succeeds', function () { beforeEach(function () { this.result = 'after-target-result'; this.target.yields(null, this.result); }); it('invokes the advice with the original arguments', function () { this.obj.target(1, 2, 3, function () {}); this.after.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes after advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the advising method errors', function () { beforeEach(function () { this.error = new Error('after-after-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the advising method succeeds', function () { beforeEach(function () { this.afterResult = 'after-after-result'; this.after.yields(null, this.afterResult); }); it('calls back with the target method results', function (done) { this.obj.target(1, 2, 3, function (err, result) { result.should.equal(this.result); done(); }.bind(this)); }); }); }); }); }); }); describe('.around', function () { describe('when the mutate option is true', function () { beforeEach(function () { this.before = sinon.stub(); this.after = sinon.stub(); this.obj.around('target', this.before, this.after, { mutate: true }); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.before.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice errors', function () { beforeEach(function () { this.error = new Error('around-mutate-before-errors'); this.before.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the target method', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.target.should.not.have.been.called; done(); }.bind(this)); }); it('does not call the after advice', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.after.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the before advice succeeds', function () { beforeEach(function () { this.resultOne = 'around-before-mutate-result-one'; this.resultTwo = 'around-before-mutate-result-two'; this.resultThree = 'around-before-mutate-result-three'; this.before.yields( null , this.resultOne , this.resultTwo , this.resultThree ); }); it('invokes the target with the before results', function () { this.obj.target(1, 2, 3, function () {}); this.target.should.have.been.calledWithExactly( this.resultOne , this.resultTwo , this.resultThree , sinon.match.func ); }); it('invokes the target in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.target.lastCall.thisValue.should.equal(this.obj); }); describe('when the target method errors', function () { beforeEach(function () { this.error = new Error('around-mutate-target-errors'); this.target.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the after advice', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.after.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the target method succeeds', function () { beforeEach(function () { this.result = 'around-mutate-target-result'; this.target.yields(null, this.result); }); it('invokes the after advice with target results', function () { this.obj.target(1, 2, 3, function () {}); this.after.should.have.been.calledWithExactly( this.result , sinon.match.func ); }); it('invokes after advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.after.lastCall.thisValue.should.equal(this.obj); }); describe('when the after advice errors', function () { beforeEach(function () { this.error = new Error('around-mutate-after-errors'); this.after.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); }); describe('when the after advice succeeds', function () { beforeEach(function () { this.afterResults = [ 'around-mutate-after-result-one' , 'around-mutate-after-result-two' ]; this.after.yields( null , this.afterResults[0] , this.afterResults[1] ); }); it('calls back with the after results', function (done) { this.obj.target(1, 2, 3, function (err, r1, r2) { [ r1, r2 ].should.deep.equal(this.afterResults); done(); }.bind(this)); }); }); }); }); }); }); describe('when the mutate option is not passed', function () { beforeEach(function () { this.before = sinon.stub(); this.after = sinon.stub(); this.obj.around('target', this.before, this.after); }); describe('when the target method is invoked', function () { it('invokes before advice with the passed arguments', function () { this.obj.target(1, 2, 3, function () {}); this.before.should.have.been.calledWithExactly( 1 , 2 , 3 , sinon.match.func ); }); it('invokes before advice in the target context', function () { this.obj.target(1, 2, 3, function () {}); this.before.lastCall.thisValue.should.equal(this.obj); }); describe('when the before advice errors', function () { beforeEach(function () { this.error = new Error('around-before-errors'); this.before.yields(this.error); }); it('calls back with the error', function (done) { this.obj.target(1, 2, 3, function (err, result) { err.should.equal(this.error); done(); }.bind(this)); }); it('does not call the target method', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.target.should.not.have.been.called; done(); }.bind(this)); }); it('does not call the after advice', function (done) { this.obj.target(1, 2, 3, function (err, result) { this.after.should.not.have.been.called; done(); }.bind(this)); }); }); describe('when the before advice succeeds', function () { beforeEach(function () { this.resultOne = 'around-before-mutate-result-one'; this.resultTwo = 'around-before-mutate-result-two'; this.resultThree = 'around-before-mutate-result-three'; this.before.yields( null , this.resultOne , this.resultTwo , this.resultThree ); }); it('invokes the target with the original arguments', function () { this.obj.target(1, 2, 3, function () {})