Skip to content

Commit

Permalink
Merge pull request #1302 from sinonjs/add-behavior
Browse files Browse the repository at this point in the history
addBehavior
  • Loading branch information
fatso83 authored Feb 26, 2017
2 parents aac0b2e + ba00de0 commit 86088bd
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 200 deletions.
4 changes: 4 additions & 0 deletions docs/release-source/release/migrating-to-2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ The following utility functions are being marked as deprecated and are planned f
## `sandbox.useFakeXMLHttpRequest` no longer returns a "server"
In Sinon 1.x, the sandbox' `useFakeXMLHttpRequest` was the same as it's `useFakeServer`. In 2.x, it maps directly to `sinon.useFakeXMLHttpRequest` (but with sandboxing). If you use `sandbox.useFakeXMLHttpRequest`, just replace it with `sandbox.useFakeServer`, and your tests should behave as they always did.
## `sinon.behavior` is gone
The `sinon.behavior` object is no longer exposed for random modification. However, there is a new mechanism in place aided to add new behavior to stubs, `sinon.addBehavior(name, fn)`, see the stub docs.
14 changes: 14 additions & 0 deletions docs/release-source/release/stubs.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,17 @@ Same as their corresponding non-Async counterparts, but with callback being defe
#### `stub.yieldsToOnAsync(property, context, [arg1, arg2, ...])`

Same as their corresponding non-Async counterparts, but with callback being deferred (executed not immediately but after short timeout and in another "thread")

#### `sinon.addBehavior(name, fn);`

Add a custom behavior. The name will be available as a function on stubs, and the chaining mechanism will be set up for you (e.g. no need to return anything from your function, it's return value will be ignored). The `fn` will be passed the fake instance as its first argument, and then the user's arguments.

```javascript
const sinon = require('sinon');

sinon.addBehavior('returnsNum', (fake, n) => fake.returns(n));

var stub = sinon.stub().returnsNum(42);

assert.equals(stub(), 42);
```
5 changes: 5 additions & 0 deletions lib/sinon.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ exports.useFakeXMLHttpRequest = fakeXhr.useFakeXMLHttpRequest;
exports.fakeServer = require("./sinon/util/fake_server");
exports.fakeServerWithClock = require("./sinon/util/fake_server_with_clock");

var behavior = require("./sinon/behavior");

exports.addBehavior = function (name, fn) {
behavior.addBehavior(exports.stub, name, fn);
};
211 changes: 21 additions & 190 deletions lib/sinon/behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,6 @@ var nextTick = (function () {
};
})();

function throwsException(error, message) {
if (typeof error === "string") {
this.exception = new Error(message || "");
this.exception.name = error;
} else if (!error) {
this.exception = new Error("Error");
} else {
this.exception = error;
}

return this.chain();
}

function getCallback(behavior, args) {
var callArgAt = behavior.callArgAt;

Expand Down Expand Up @@ -114,6 +101,8 @@ var proto = {
create: function create(stub) {
var behavior = extend({}, proto);
delete behavior.create;
delete behavior.addBehavior;
delete behavior.createBehavior;
behavior.stub = stub;

return behavior;
Expand Down Expand Up @@ -173,183 +162,6 @@ var proto = {
);
},

callsFake: function callsFake(fn) {
this.fakeFn = fn;
return this.chain();
},

callsArg: function callsArg(pos) {
if (typeof pos !== "number") {
throw new TypeError("argument index is not number");
}

this.callArgAt = pos;
this.callbackArguments = [];
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

callsArgOn: function callsArgOn(pos, context) {
if (typeof pos !== "number") {
throw new TypeError("argument index is not number");
}

this.callArgAt = pos;
this.callbackArguments = [];
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

callsArgWith: function callsArgWith(pos) {
if (typeof pos !== "number") {
throw new TypeError("argument index is not number");
}

this.callArgAt = pos;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

callsArgOnWith: function callsArgWith(pos, context) {
if (typeof pos !== "number") {
throw new TypeError("argument index is not number");
}

this.callArgAt = pos;
this.callbackArguments = slice.call(arguments, 2);
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

yields: function () {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

yieldsRight: function () {
this.callArgAt = useRightMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

yieldsOn: function (context) {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this.chain();
},

yieldsTo: function (prop) {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = undefined;
this.callArgProp = prop;
this.callbackAsync = false;

return this.chain();
},

yieldsToOn: function (prop, context) {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 2);
this.callbackContext = context;
this.callArgProp = prop;
this.callbackAsync = false;

return this.chain();
},

throws: throwsException,
throwsException: throwsException,

returns: function returns(value) {
this.returnValue = value;
this.resolve = false;
this.reject = false;
this.returnValueDefined = true;
this.exception = undefined;
this.fakeFn = undefined;

return this.chain();
},

returnsArg: function returnsArg(pos) {
if (typeof pos !== "number") {
throw new TypeError("argument index is not number");
}

this.returnArgAt = pos;

return this.chain();
},

returnsThis: function returnsThis() {
this.returnThis = true;

return this.chain();
},

resolves: function resolves(value) {
this.returnValue = value;
this.resolve = true;
this.reject = false;
this.returnValueDefined = true;
this.exception = undefined;
this.fakeFn = undefined;

return this.chain();
},

rejects: function rejects(error, message) {
var reason;
if (typeof error === "string") {
reason = new Error(message || "");
reason.name = error;
} else if (!error) {
reason = new Error("Error");
} else {
reason = error;
}
this.returnValue = reason;
this.resolve = false;
this.reject = true;
this.returnValueDefined = true;
this.exception = undefined;
this.fakeFn = undefined;

return this;
},

callThrough: function callThrough() {
this.callsThrough = true;
return this.chain();
},

chain: function chain() {
/**
* "this" is stub when method is called directly on stub, e.g. stub.returns(123);
Expand All @@ -375,4 +187,23 @@ Object.keys(proto).forEach(function (method) {
}
});

function createBehavior(behaviorMethod) {
return function () {
this.defaultBehavior = this.defaultBehavior || proto.create(this);
this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);
return this;
};
}

function addBehavior(stub, name, fn) {
proto[name] = function () {
fn.apply(this, [this].concat([].slice.call(arguments)));
return this.stub || this;
};

stub[name] = createBehavior(name);
}

proto.addBehavior = addBehavior;
proto.createBehavior = createBehavior;
module.exports = proto;
Loading

0 comments on commit 86088bd

Please sign in to comment.