Skip to content

Commit

Permalink
Add support for asserting on sinon.createStubInstance(Constructor);
Browse files Browse the repository at this point in the history
(or any object that has spies as direct or prototypal properties).

Applies to these assertions:

    to have calls [exhaustively] satisfying
    to have a call [exhaustively] satisfying
    to have no calls [exhaustively] satisfying

Gathers all spies attached to the object and asserts on the complete
timeline.
  • Loading branch information
papandreou committed Jan 24, 2017
1 parent 4925330 commit 2096802
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 18 deletions.
92 changes: 76 additions & 16 deletions lib/unexpected-sinon.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,15 +428,60 @@
}
});

function extractSpiesFromSinonStubInstance(stubInstance) {
var spies = [];
for (var propertyName in stubInstance) {
if (isSpy(stubInstance[propertyName])) {
spies.push(stubInstance[propertyName]);
}
}
if (spies.length === 0) {
throw new Error('The passed object was not recognized as a sinon "stub instance" as it has no spies attached to it');
}
return spies;
}

function extractSpies(spyOrArrayOrSinonSandbox) {
if (expect.findTypeOf(spyOrArrayOrSinonSandbox).is('sinonSandbox')) {
return spyOrArrayOrSinonSandbox.fakes || [];
} else if (Array.isArray(spyOrArrayOrSinonSandbox)) {
return spyOrArrayOrSinonSandbox;
} else if (spyOrArrayOrSinonSandbox && typeof spyOrArrayOrSinonSandbox === 'object') {
return extractSpiesFromSinonStubInstance(spyOrArrayOrSinonSandbox);
} else {
return Array.isArray(spyOrArrayOrSinonSandbox) ? spyOrArrayOrSinonSandbox : [ spyOrArrayOrSinonSandbox ];
// Assume spy
return [ spyOrArrayOrSinonSandbox ];
}
}

function overrideSubjectOutputIfStubInstance(subject, expect, spiesIfAvailable) {
if (expect.subjectType.name === 'object') {
// Replace "stubbed instance" subject with: ClassName({spy1, spy2, spy3 /* 2 more */})
expect.subjectOutput = function (output) {
var spies = spiesIfAvailable || extractSpies(subject);
output.jsFunctionName(subject.constructor.name || 'sinonStubInstance')
.text('({');
var width = 0;
for (var i = 0 ; i < spies.length ; i += 1) {
var spy = spies[i];
var itemWidth = (i > 0 ? 2 : 0) + (spy.displayName ? spy.displayName.length : 4);
if ((width + itemWidth) < (expect.output.preferredWidth - 40)) {
if (i > 0) {
output.text(',').sp();
}
output.appendInspected(spy);
width += itemWidth;
} else {
output.sp().jsComment('/* ' + (spies.length - i) + ' more */');
break;
}
}
output.text('})');
};
}
}

expect.addAssertion('<spy|array|sinonSandbox> to have calls [exhaustively] satisfying <function>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have calls [exhaustively] satisfying <function>', function (expect, subject, value) {
var spies = extractSpies(subject);
var expectedSpyCallSpecs = recordSpyCalls(spies, value);
var expectedSpyCalls = [];
Expand All @@ -448,10 +493,11 @@
expect.argsOutput[0] = function (output) {
output.appendInspected(expectedSpyCalls);
};
return expect(spies, 'to have calls [exhaustively] satisfying', expectedSpyCallSpecs);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
return expect(subject, 'to have calls [exhaustively] satisfying', expectedSpyCallSpecs);
});

expect.addAssertion('<spy|array|sinonSandbox> to have calls [exhaustively] satisfying <array|object>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have calls [exhaustively] satisfying <array|object>', function (expect, subject, value) {
var spies = extractSpies(subject);
var spyCalls = [];
var isSeenBySpyId = {};
Expand All @@ -465,6 +511,8 @@
return a.callId - b.callId;
});

overrideSubjectOutputIfStubInstance(subject, expect, spies);

var seenSpies = [];
function wrapSpyInObject(obj) {
if (isSpy(obj)) {
Expand Down Expand Up @@ -564,8 +612,9 @@
}
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls [exhaustively] satisfying <object>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have no calls [exhaustively] satisfying <object>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var keys = Object.keys(value);
if (
keys.length > 0 &&
Expand Down Expand Up @@ -598,12 +647,15 @@
});
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls [exhaustively] satisfying <array>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have no calls [exhaustively] satisfying <array>', function (expect, subject, value) {
overrideSubjectOutputIfStubInstance(subject, expect);
return expect(subject, 'to have no calls [exhaustively] satisfying', { args: value });
});

expect.addAssertion('<spy|array|sinonSandbox> to have no calls satisfying <function>', function (expect, subject, value) {
var expectedSpyCallSpecs = recordSpyCalls(extractSpies(subject), value);
expect.addAssertion('<spy|array|sinonSandbox|object> to have no calls satisfying <function>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var expectedSpyCallSpecs = recordSpyCalls(spies, value);
var expectedSpyCalls = [];
expectedSpyCallSpecs.forEach(function (expectedSpyCallSpec) {
expectedSpyCalls.push(expectedSpyCallSpec.call);
Expand All @@ -622,8 +674,9 @@
return expect(subject, 'to have no calls satisfying', expectedSpyCallSpecs[0]);
});

expect.addAssertion('<spy|array|sinonSandbox> to have a call [exhaustively] satisfying <object>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have a call [exhaustively] satisfying <object>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var keys = Object.keys(value);
if (
keys.length > 0 &&
Expand Down Expand Up @@ -654,12 +707,15 @@
});
});

expect.addAssertion('<spy|array|sinonSandbox> to have a call [exhaustively] satisfying <array>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have a call [exhaustively] satisfying <array>', function (expect, subject, value) {
overrideSubjectOutputIfStubInstance(subject, expect);
return expect(subject, 'to have a call [exhaustively] satisfying', { args: value });
});

expect.addAssertion('<spy|array|sinonSandbox> to have a call satisfying <function>', function (expect, subject, value) {
var expectedSpyCallSpecs = recordSpyCalls(extractSpies(subject), value);
expect.addAssertion('<spy|array|sinonSandbox|object> to have a call satisfying <function>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var expectedSpyCallSpecs = recordSpyCalls(spies, value);
var expectedSpyCalls = [];
expectedSpyCallSpecs.forEach(function (expectedSpyCallSpec) {
expectedSpyCalls.push(expectedSpyCallSpec.call);
Expand All @@ -678,8 +734,9 @@
return expect(subject, 'to have a call satisfying', expectedSpyCallSpecs[0]);
});

expect.addAssertion('<spy|array|sinonSandbox> to have all calls [exhaustively] satisfying <object>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have all calls [exhaustively] satisfying <object>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var keys = Object.keys(value);
if (
keys.length > 0 &&
Expand All @@ -695,12 +752,15 @@
return expect(getCallTimeLineFromSpies(spies), 'to have items [exhaustively] satisfying', value);
});

expect.addAssertion('<spy|array|sinonSandbox> to have all calls [exhaustively] satisfying <array>', function (expect, subject, value) {
expect.addAssertion('<spy|array|sinonSandbox|object> to have all calls [exhaustively] satisfying <array>', function (expect, subject, value) {
overrideSubjectOutputIfStubInstance(subject, expect);
return expect(subject, 'to have all calls [exhaustively] satisfying', { args: value });
});

expect.addAssertion('<spy|array|sinonSandbox> to have all calls satisfying <function>', function (expect, subject, value) {
var expectedSpyCallSpecs = recordSpyCalls(extractSpies(subject), value);
expect.addAssertion('<spy|array|sinonSandbox|object> to have all calls satisfying <function>', function (expect, subject, value) {
var spies = extractSpies(subject);
overrideSubjectOutputIfStubInstance(subject, expect, spies);
var expectedSpyCallSpecs = recordSpyCalls(spies, value);
var expectedSpyCalls = [];
expectedSpyCallSpecs.forEach(function (expectedSpyCallSpec) {
expectedSpyCalls.push(expectedSpyCallSpec.call);
Expand Down
14 changes: 12 additions & 2 deletions test/monkeyPatchSinonStackFrames.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ module.exports = function (sinon) {
};
}


['spy', 'stub'].forEach(function (name) {
var orig = sinon[name];
sinon[name] = function () {
sinon[name] = function () { // ...
var result = orig.apply(this, arguments);
if (isSpy(result)) {
patchSpy(result);
Expand All @@ -40,4 +39,15 @@ module.exports = function (sinon) {
};
sinon[name].create = orig.create;
});

var origCreateStubInstance = sinon.createStubInstance;
sinon.createStubInstance = function () { // ...
var instance = origCreateStubInstance.apply(this, arguments);
for (var propertyName in instance) {
if (isSpy(instance[propertyName])) {
patchSpy(instance[propertyName]);
}
}
return instance;
};
};
27 changes: 27 additions & 0 deletions test/tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
unexpected.output.preferredWidth = 80;

// Copied from test/monkeyPatchSinonStackFrames.js
function isSpy(value) {
return value && typeof value.id === 'string' &&
/^spy#/.test(value.id);
}

function patchCall(call) {
var getStackFrames = call && call.getStackFrames;
if (getStackFrames) {
Expand All @@ -27,6 +32,17 @@
return call;
}

function patchSpy(spy) {
var getCall = spy.getCall;
spy.getCall = function () {
return patchCall(getCall.apply(spy, arguments));
};
var getCalls = spy.getCalls;
spy.getCalls = function () {
return getCalls.call(spy).map(patchCall);
};
}

['spy', 'stub'].forEach(function (name) {
var orig = sinon[name];
sinon[name] = function () {
Expand All @@ -43,6 +59,17 @@
};
sinon[name].create = orig.create;
});

var origCreateStubInstance = sinon.createStubInstance;
sinon.createStubInstance = function () { // ...
var instance = origCreateStubInstance.apply(this, arguments);
for (var propertyName in instance) {
if (isSpy(instance[propertyName])) {
patchSpy(instance[propertyName]);
}
}
return instance;
};
</script>
<script src="unexpected-sinon.spec.js"></script>
<script>
Expand Down
Loading

0 comments on commit 2096802

Please sign in to comment.