Skip to content

Commit

Permalink
refactor: extract and share common tests for spies/stubs
Browse files Browse the repository at this point in the history
Remove the wrongly placed test in assert-test.js.

Also fixes comment regarding missing tests for Object.prototype:
#2027 (comment)

fix: spelling
re #2027 (comment)
  • Loading branch information
fatso83 committed Sep 23, 2019
1 parent 181dcdd commit 9c4bbf1
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 149 deletions.
2 changes: 1 addition & 1 deletion docs/release-source/release/spies.md
Expand Up @@ -40,7 +40,7 @@ Spies all the object's methods.

Note that it's usually better practice to spy individual methods, particularly on objects that you don't understand or control all the methods for (e.g. library dependencies).

spying individual methods tests intent more precisely and is less susceptible to unexpected behavior as the object's code evolves.
Spying individual methods tests intent more precisely and is less susceptible to unexpected behavior as the object's code evolves.

The following is a slightly contrived example:

Expand Down
6 changes: 3 additions & 3 deletions lib/sinon/stub.js
Expand Up @@ -36,11 +36,11 @@ function stub(object, property) {
}

var actualDescriptor = getPropertyDescriptor(object, property);
var isObject = typeof object === "object" || typeof object === "function";
var isStubbingEntireObject = typeof property === "undefined" && isObject;
var isObjectOrFunction = typeof object === "object" || typeof object === "function";
var isStubbingEntireObject = typeof property === "undefined" && isObjectOrFunction;
var isCreatingNewStub = !object && typeof property === "undefined";
var isStubbingNonFuncProperty =
isObject &&
isObjectOrFunction &&
typeof property !== "undefined" &&
(typeof actualDescriptor === "undefined" || typeof actualDescriptor.value !== "function") &&
typeof descriptor === "undefined";
Expand Down
19 changes: 0 additions & 19 deletions test/assert-test.js
Expand Up @@ -54,25 +54,6 @@ describe("assert", function() {
});
});

it("supports no properties", function() {
var api = {
method1: function() {
return;
},
method2: function() {
return;
}
};
sinonSpy(api);
api.method1();
api.method2();

refute.exception(function() {
sinonAssert.calledOnce(api.method1);
sinonAssert.calledOnce(api.method2);
});
});

describe(".fail", function() {
beforeEach(function() {
this.exceptionName = sinonAssert.failException;
Expand Down
134 changes: 134 additions & 0 deletions test/shared-spy-stub-everything-tests.js
@@ -0,0 +1,134 @@
"use strict";

var referee = require("@sinonjs/referee");
var assert = referee.assert;
var refute = referee.refute;

module.exports = function shared(createSpyOrStub) {
it("replaces all methods of an object when no property is given", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
},
func3: function() {
return;
}
};

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("replaces prototype methods", function() {
function Obj() {
return;
}
Obj.prototype.func1 = function() {
return;
};
var obj = new Obj();

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
});

it("returns object", function() {
var object = {};

assert.same(createSpyOrStub(object), object);
});

it("only replaces functions", function() {
var object = { foo: "bar" };
createSpyOrStub(object);

assert.equals(object.foo, "bar");
});

it("handles non-enumerable properties", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
}
};

Object.defineProperty(obj, "func3", {
value: function() {
return;
},
writable: true,
configurable: true
});

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("handles non-enumerable properties on prototypes", function() {
function Obj() {
return;
}
Object.defineProperty(Obj.prototype, "func1", {
value: function() {
return;
},
writable: true,
configurable: true
});

var obj = new Obj();

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
});

it("does not replace non-enumerable properties from Object.prototype", function() {
var obj = {};

createSpyOrStub(obj);

refute.isFunction(obj.toString.restore);
refute.isFunction(obj.toLocaleString.restore);
refute.isFunction(obj.propertyIsEnumerable.restore);
});

it("does not fail on overrides", function() {
var parent = {
func: function() {
return;
}
};
var child = Object.create(parent);
child.func = function() {
return;
};

refute.exception(function() {
createSpyOrStub(child);
});
});

it("throws on non-existent property", function() {
var myObj = {};

assert.exception(function() {
createSpyOrStub(myObj, "ouch");
});

refute.defined(myObj.ouch);
});
};
4 changes: 4 additions & 0 deletions test/spy-test.js
Expand Up @@ -2931,4 +2931,8 @@ describe("spy", function() {
assert.equals(Object.keys(spy), ["aProp"]);
});
});

describe("everything", function() {
require("./shared-spy-stub-everything-tests")(createSpy);
});
});
127 changes: 1 addition & 126 deletions test/stub-test.js
Expand Up @@ -1275,45 +1275,7 @@ describe("stub", function() {
});

describe("everything", function() {
it("stubs all methods of object without property", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
},
func3: function() {
return;
}
};

createStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("stubs prototype methods", function() {
function Obj() {
return;
}
Obj.prototype.func1 = function() {
return;
};
var obj = new Obj();

createStub(obj);

assert.isFunction(obj.func1.restore);
});

it("returns object", function() {
var object = {};

assert.same(createStub(object), object);
});
require("./shared-spy-stub-everything-tests")(createStub);

it("returns function", function() {
var func = function() {
Expand Down Expand Up @@ -1341,83 +1303,6 @@ describe("stub", function() {
assert.isFunction(func.func2.restore);
});

it("only stubs functions", function() {
var object = { foo: "bar" };
createStub(object);

assert.equals(object.foo, "bar");
});

it("handles non-enumerable properties", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
}
};

Object.defineProperty(obj, "func3", {
value: function() {
return;
},
writable: true,
configurable: true
});

createStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("handles non-enumerable properties on prototypes", function() {
function Obj() {
return;
}
Object.defineProperty(Obj.prototype, "func1", {
value: function() {
return;
},
writable: true,
configurable: true
});

var obj = new Obj();

createStub(obj);

assert.isFunction(obj.func1.restore);
});

it("does not stub non-enumerable properties from Object.prototype", function() {
var obj = {};

createStub(obj);

refute.isFunction(obj.toString.restore);
refute.isFunction(obj.toLocaleString.restore);
refute.isFunction(obj.propertyIsEnumerable.restore);
});

it("does not fail on overrides", function() {
var parent = {
func: function() {
return;
}
};
var child = Object.create(parent);
child.func = function() {
return;
};

refute.exception(function() {
createStub(child);
});
});

it("does not call getter during restore", function() {
var obj = {
get prop() {
Expand All @@ -1433,16 +1318,6 @@ describe("stub", function() {

stub.restore();
});

it("throws if stubbing non-existent property", function() {
var myObj = {};

assert.exception(function() {
createStub(myObj, "ouch");
});

refute.defined(myObj.ouch);
});
});

describe("stubbed function", function() {
Expand Down

0 comments on commit 9c4bbf1

Please sign in to comment.