Skip to content

Commit

Permalink
add onCall to stubs for building sequences (sinonjs#244)
Browse files Browse the repository at this point in the history
  • Loading branch information
tf committed Feb 19, 2013
1 parent 7fb204d commit 2d0b6f4
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 37 deletions.
63 changes: 47 additions & 16 deletions lib/sinon/stub.js
Expand Up @@ -56,11 +56,11 @@
var msg;

if (behavior.callArgProp) {
msg = behavior.stubFunctionName +
msg = sinon.functionName(behavior.stub) +
" expected to yield to '" + behavior.callArgProp +
"', but no object with such a property was passed."
} else {
msg = behavior.stubFunctionName +
msg = sinon.functionName(behavior.stub) +
" expected to yield, but no callback was passed."
}

Expand Down Expand Up @@ -93,10 +93,10 @@
}

proto = {
create: function(stubFunctionName) {
create: function(stub) {
var behavior = sinon.extend({}, sinon.behavior);
delete behavior.create;
behavior.stubFunctionName = stubFunctionName;
behavior.stub = stub;

return behavior;
},
Expand All @@ -105,6 +105,22 @@
callCallback(this, args);
},

onCall: function(index) {
return this.stub.onCall(index);
},

onFirstCall: function() {
return this.stub.onFirstCall();
},

onSecondCall: function() {
return this.stub.onSecondCall();
},

onThirdCall: function() {
return this.stub.onThirdCall();
},

callsArg: function callsArg(pos) {
if (typeof pos != "number") {
throw new TypeError("argument index is not number");
Expand Down Expand Up @@ -208,7 +224,6 @@
}
};


// create asynchronous versions of callsArg* and yields* methods
for (var method in proto) {
// need to avoid creating anotherasync versions of the newly added async methods
Expand Down Expand Up @@ -257,12 +272,8 @@
return sinon.wrapMethod(object, property, wrapper);
}

function getDefaultBehavior(stub) {
return stub.behaviors[stub.behaviors.length - 1] || sinon.behavior.create(sinon.functionName(stub));
}

function getCurrentBehavior(stub) {
return stub.behaviors[stub.callCount - 1] || getDefaultBehavior(stub);
return stub.behaviors[stub.callCount - 1] || stub.defaultBehavior;
}

var join = Array.prototype.join;
Expand Down Expand Up @@ -318,19 +329,21 @@
functionStub = sinon.spy.create(functionStub);
functionStub.func = orig;

functionStub.behaviors = [];

sinon.extend(functionStub, stub);
functionStub._create = sinon.stub.create;
functionStub.displayName = "stub";
functionStub.toString = sinon.functionToString;

functionStub.defaultBehavior = sinon.behavior.create(functionStub);
functionStub.behaviors = [];

return functionStub;
},

resetBehavior: function () {
var i;

this.defaultBehavior = sinon.behavior.create(this);
this.behaviors = [];

delete this.returnValue;
Expand All @@ -344,6 +357,26 @@
}
},

onCall: function(index) {
if (!this.behaviors[index]) {
this.behaviors[index] = sinon.behavior.create(this);
}

return this.behaviors[index];
},

onFirstCall: function() {
return this.onCall(0);
},

onSecondCall: function() {
return this.onCall(1);
},

onThirdCall: function() {
return this.onCall(2);
},

returns: function returns(value) {
this.returnValue = value;

Expand Down Expand Up @@ -372,14 +405,12 @@

for (var method in sinon.behavior) {
if (sinon.behavior.hasOwnProperty(method) &&
!proto.hasOwnProperty(method) &&
method != 'create' &&
method != 'invoke') {
proto[method] = (function(behaviorMethod) {
return function() {
var behavior = sinon.behavior.create(sinon.functionName(this));
behavior[behaviorMethod].apply(behavior, arguments);
this.behaviors.push(behavior);

this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);
return this;
};
}(method));
Expand Down
44 changes: 23 additions & 21 deletions test/sinon/stub_test.js
Expand Up @@ -1197,14 +1197,15 @@ buster.testCase("sinon.stub", {
}
},

"yields* calls should be chainable to produce a sequence": function () {
"onCall and yields* should be chainable to produce a sequence": function () {
var context = { foo: "bar" };
var obj = { method1: sinon.spy(), method2: sinon.spy() };
var obj2 = { method2: sinon.spy() };
var stub = sinon.stub().yields(1, 2)
.yieldsOn(context, 3, 4)
.yieldsTo("method1", 5, 6)
.yieldsToOn("method2", context, 7, 8);
var stub = sinon.stub().yieldsToOn("method2", context, 7, 8);
stub.onFirstCall().yields(1, 2)
.onSecondCall().yieldsOn(context, 3, 4)
.onThirdCall().yieldsTo("method1", 5, 6)
.onCall(3).yieldsToOn("method2", context, 7, 8);

var spy1 = sinon.spy();
var spy2 = sinon.spy();
Expand All @@ -1213,7 +1214,7 @@ buster.testCase("sinon.stub", {
stub(spy2);
stub(obj);
stub(obj);
stub(obj2); // should continue doing the last thing
stub(obj2); // should continue with default behavior

assert(spy1.calledOnce);
assert(spy1.calledWithExactly(1, 2));
Expand All @@ -1238,7 +1239,7 @@ buster.testCase("sinon.stub", {
assert(obj2.method2.calledWithExactly(7, 8));
},

"callsArg* calls should be chainable to produce a sequence": function () {
"onCall and callsArg* should be chainable to produce a sequence": function () {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
var spy3 = sinon.spy();
Expand All @@ -1247,16 +1248,17 @@ buster.testCase("sinon.stub", {
var decoy = sinon.spy();
var context = { foo: "bar" };

var stub = sinon.stub().callsArg(0)
.callsArgWith(1, "a", "b")
.callsArgOn(2, context)
.callsArgOnWith(3, context, "c", "d");
var stub = sinon.stub().callsArgOnWith(3, context, "c", "d");
stub.onFirstCall().callsArg(0)
.onSecondCall().callsArgWith(1, "a", "b")
.onThirdCall().callsArgOn(2, context)
.onCall(3).callsArgOnWith(3, context, "c", "d");

stub(spy1);
stub(decoy, spy2);
stub(decoy, decoy, spy3);
stub(decoy, decoy, decoy, spy4);
stub(decoy, decoy, decoy, spy5); // should continue doing last thing
stub(decoy, decoy, decoy, spy5); // should continue with default behavior

assert(spy1.calledOnce);

Expand All @@ -1281,11 +1283,11 @@ buster.testCase("sinon.stub", {
assert(decoy.notCalled);
},

"yields* calls and callsArg* in combination should be chainable to produce a sequence": function () {
var stub = sinon.stub().yields(1, 2)
.callsArg(1)
.yieldsTo("method")
.callsArgWith(2, "a", "b");
"onCall, yields* and callsArg* in combination should be chainable to produce a sequence": function () {
var stub = sinon.stub().yields(1, 2);
stub.onSecondCall().callsArg(1)
.onThirdCall().yieldsTo("method")
.onCall(3).callsArgWith(2, "a", "b");

var obj = { method: sinon.spy() };
var spy1 = sinon.spy();
Expand Down Expand Up @@ -1313,7 +1315,7 @@ buster.testCase("sinon.stub", {
assert(decoy.notCalled);
},

"callsArgWith sequences should interact correctly with assertions (GH-231)": function () {
"onCall sequences should interact correctly with assertions (GH-231)": function () {
var stub = sinon.stub();
var spy = sinon.spy();

Expand All @@ -1325,7 +1327,7 @@ buster.testCase("sinon.stub", {
stub(spy);
assert(spy.calledWith("a"));

stub.callsArgWith(0, "b");
stub.onThirdCall().callsArgWith(0, "b");

stub(spy);
assert(spy.calledWith("b"));
Expand All @@ -1345,8 +1347,8 @@ buster.testCase("sinon.stub", {

"resetBehavior": {
"clears yields* and callsArg* sequence": function () {
var stub = sinon.stub().yields(1)
.callsArg(1);
var stub = sinon.stub().yields(1);
stub.onFirstCall().callsArg(1);
stub.resetBehavior();
stub.yields(3);
var spyWanted = sinon.spy();
Expand Down

0 comments on commit 2d0b6f4

Please sign in to comment.