I'd like to implement this myself, if there is interest. Our tests would benefit from a way to await calls to sinon spies. I'm not married to the name, but here's the proposed API:
let spy = sinon.spy()
setTimeout(() => spy(0), 10)
spy.promisedCall(0).then((call) => {
expect(call.args[0]).to.equal(0)
setTimeout(() => spy(1), 10)
return spy.promisedCall(1)
}).then((call) => {
expect(call.args[0]).to.equal(1)
})
This would be particularly useful for testing callback heavy code (i.e. React) in promise-aware frameworks like mocha, jest, and blue-tape:
describe('test that callback was called on press', async () => {
const props = makeProps()
const wrapper = shallow(<ExampleComponent {...props} />)
wrapper.find('Button').simulate('click')
const call = await props.onClickSpy.promisedCall(0)
expect(call.args).to.be.something
})
Instead of having to wrangle setTimeout, promises and await, wouldn't you rather just have a way to control the passage of time?
Sinon.JS has fake timers for this purpose, they have even been extracted into the lolex module, so you don't need Sinon.JS :)
Would that be able to provide an elegant solution for you?
Timers don't really do what I need here. The setTimeout example was just one example. A better example is the react component, where I need to wait for the future call to onClickSpy. Unless I'm missing something, there's no way to achieve that with timers?
@AnIrishDuck, I was not 100% sure what was trying to be accomplished after reading both the explanation and the test code, but after digging a little further into it I am guessing this is basically just a Promise-friendly way of handling promise-based callback code?
Just to clarify, I would assume your last test would be written like this today:
describe('test that callback was called on press', (done) => {
const props = makeProps() // creates a stub on props.onClick
props.onClick.callsFake((foo, bar)=> {
expect(foo).to.be.something;
// alternatively
// expect(props.onClick.firstCall.arguments).to.be.something;
done();
});
const wrapper = shallow(<ExampleComponent {...props} />)
wrapper.find('Button').simulate('click')
})
Right?
In that case, I assume your test could be written like this today to make use of promise-support in the framework:
describe('promise based test that callback was called on press', () => {
return new Promise((resolve, reject) => {
const props = makeProps() // creates a stub on props.onClick
props.onClick.callsFake((foo, bar)=> {
expect(foo).to.be.something;
resolve();
});
const wrapper = shallow(<ExampleComponent {...props} />)
wrapper.find('Button').simulate('click')
})
})
Per your API, I assume spy.promisedCall(100) would immediately return a Promise that would resolve on the 100th call to the spy, and whose resolved value (at that time in the future) would be the same as spy.getCall(100).
Do I understand you correctly?
@fatso83 - correct. After re-reading my example, I realize why it was so confusing. I used some of our internal test code conventions without explaining them.
The proposed API would make our Mocha code much more concise, especially because we use async / await. This is what the example looks like in that case:
describe('promise based test that callback was called on press', async () => {
const props = makeProps() // creates stub react properties
props.onClick = sinon.spy() // this could theoretically be done in makeProps() if it's used often enough
const wrapper = shallow(<ExampleComponent {...props} />)
wrapper.find('Button').simulate('click')
const foo = await props.onClick.promisedCall(0)
expect(foo.args).to.equal(['something'])
})
Even without async / await support, the code is still nicer:
describe('promise based test that callback was called on press', () => {
const props = makeProps() // creates stub react properties
props.onClick = sinon.spy() // this could theoretically be done in makeProps() if it's used often enough
const wrapper = shallow(<ExampleComponent {...props} />)
wrapper.find('Button').simulate('click')
return props.onClick.promisedCall(0).then((foo) => {
expect(foo.args).to.equal(['something'])
})
})
If I get a 馃憤 from a contributor that would be willing to review a PR for this, I'll get started.
Just to be sure I understand the last test case, if simulate is synchronous or asynchronous, the test will pass in either case, as either the promise will resolve immediately or it will resolve at some later time, right? To me, this looks like a nice addition, although I am not loving the method name, so please go ahead.
Correct, that's part of the magic of promises. They store the resolved value for future use. So using await on a promise that has already resolved is no big deal.
I'm not a huge fan of the name either. I'll see if I can come up with something better. Originally I was going to use futureCall, but that doesn't really fit either because of what you just pointed out.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
So, what actually happened to this in the end?
We have some spy function that is passed into a component. We simulate a click, This makes our component run through it's click code, the component does some async tasks, and then calls our passed in function (which is our spy function).
But because the async is all internal to the component, we don't have access to the promise to await it to test spy at the approprate time.
Most helpful comment
If I get a 馃憤 from a contributor that would be willing to review a PR for this, I'll get started.