What did you expect to happen?
The mock expectation should support the full stub API, including callThrough. The mocked expectation should invoke the actual app code.
What actually happens
An error is thrown trying to use the callThrough method because this.stub.wrappedMethod appears to be undefined.
How to reproduce
describe('test', function () {
it('should be ok', function () {
const transaction = Object.create({
commit: function () { return Promise.reject(); },
rollback: function () { return Promise.resolve(); }
});
function appCode() {
return transaction.commit();
}
const mock = sinon.mock(transaction);
mock.expects('commit').once().callThrough();
return expect(appCode()).to.eventually.be.fulfilled.then(() => mock.verify())
});
});
Stack Trace
TypeError: Cannot read property 'apply' of undefined
at Object.invoke (node_modules/sinon/lib/sinon/behavior.js:146:43)
at Object.functionStub (node_modules/sinon/lib/sinon/stub.js:88:53)
at Function.invoke (node_modules/sinon/lib/sinon/spy.js:194:51)
at Function.invoke (node_modules/sinon/lib/sinon/mock-expectation.js:80:26)
at Object.proxy (node_modules/sinon/lib/sinon/spy.js:97:22)
at Object.invokeMethod (node_modules/sinon/lib/sinon/mock.js:132:43)
at Object.commit (node_modules/sinon/lib/sinon/mock.js:66:35)
at appCode (test/unit/graphql/models/Workflow.test.js:40:36)
I've encountered the same error, as well as an additional one to go with it, so I distilled it down to as small an example as I could. The following example shows the error @jamespedid cites above, as well as shows that callThrough() is not behaving as expected. I'll leave it to the maintainer to decide whether these are related:
let sinon = require('sinon');
let obj = { query: function() { return 'foo'; } };
sinon.stub(obj, 'query')
.withArgs(123)
.returns('stubbed')
.callThrough();
console.log(obj.query()); // prints "undefined"
console.log(obj.query(123)); // throws error
stack trace
โฏ node sinon-test.js
undefined
/Users/tony/development/innovu/app-server/node_modules/sinon/lib/sinon/spy.js:222
throw exception;
^
TypeError: Cannot read property 'apply' of undefined
at Object.invoke (node_modules/sinon/lib/sinon/behavior.js:146:43)
at Object.functionStub (node_modules/sinon/lib/sinon/stub.js:88:53)
at Function.invoke (node_modules/sinon/lib/sinon/spy.js:194:51)
at Function.invoke (node_modules/sinon/lib/sinon/spy.js:192:40)
at Object.proxy [as query] (node_modules/sinon/lib/sinon/spy.js:97:22)
at Object.<anonymous> (sinon-test.js:11:17)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
So both the withArgs() and callThrough() are having problems for me.
A little more poking around has shown this to be specific to withArgs() for me. In the absence of withArgs() the callThrough() function works as expected. It's as though the wrappedMethod is being lost when withArgs() is applied.
OK, so I worked around the problem in a way that should give someone a little more knowledgeable in the sinon codebase something to go on. Everything on my end seems to work if callThrough() is called in the chain BEFORE withArgs(), or presumably any other fake generating code.
this ends in a crash:
let stub = sinon.stub(obj, 'query')
.withArgs(123)
.returns('bar')
.callThrough();
while this behaves exactly as expected:
let stub = sinon.stub(obj, 'query')
.callThrough()
.withArgs(123)
.returns('bar');
So it seems, IMHO, the callThrough() should be smart enough to traverse through the parent hierarchy to find the root of the stub and use its wrappedMethod(). I _believe_ the problem section is here:
https://github.com/sinonjs/sinon/blob/master/lib/sinon/behavior.js#L146
This code should perhaps traverse up the stub parents to find the wrappedMethod when in a chaining scenario, which I believe would fix this bug. If this is a satisfactory approach I could put together a PR, but I'd feel better about getting a maintainers opinion on this approach to solving the problem.
In he meantime, call callThrough() before other fake functions in your chain and you should be good.
bump. Any thoughts from maintainers/contributors here?
Thanks for taking the time to dig into this, Tony! Not intentionally meaning to leave you hanging. It's just that stuff like this is time consuming to debug, and when it doesn't affect oneself there are often other issues that are more compelling (ie more issues closed per day).
I don't have time to look into this right now, but if you manage to make a PR that fixes the issue then it's easier to reason around anyway.
I think when expectation is initiated and the method is wrapped, wrapMethod props do not extend expectation (https://github.com/sinonjs/sinon/blob/master/lib/sinon/mock.js#L66)
I believe that
var expectation = mockExpectation.create(method);
should be rewritten to
var expectation = mockExpectation.create(method);
extend(expectation, this.object[method]);
Most helpful comment
OK, so I worked around the problem in a way that should give someone a little more knowledgeable in the sinon codebase something to go on. Everything on my end seems to work if
callThrough()is called in the chain BEFOREwithArgs(), or presumably any otherfakegenerating code.this ends in a crash:
while this behaves exactly as expected:
So it seems, IMHO, the
callThrough()should be smart enough to traverse through theparenthierarchy to find the root of the stub and use itswrappedMethod(). I _believe_ the problem section is here:https://github.com/sinonjs/sinon/blob/master/lib/sinon/behavior.js#L146
This code should perhaps traverse up the
stubparents to find thewrappedMethodwhen in a chaining scenario, which I believe would fix this bug. If this is a satisfactory approach I could put together a PR, but I'd feel better about getting a maintainers opinion on this approach to solving the problem.In he meantime, call
callThrough()before otherfakefunctions in your chain and you should be good.