-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
calling runAllTimers after using Lodash's _.debounce results in an infinite recursion error #3465
Comments
Btw, I hit this too. Turns out lodash's implementation of throttle is way more complex than it (imo) should be. Ended up with a simple helper like this one because I didn't have time to debug what's wrong. |
Lodash throttles by way of debounce. It's robust and handles things like clock drift after daylights savings time. That said, IMO it's not really Lodash's burden to prop up a mock library. We do our part to be good neighbors and don't hold on to timer references like |
@jdalton thanks for your input! |
Since this is a recursive issue you might look into a non-recursive mock that uses a while loop and a queue array of |
@rimunroe I had the same error when using |
jest.runOnlyPendingTimers() eliminates the error message for me, but the method is never invoked. |
I have the same issue, no error message but the method is not invoked. |
Managed to work around this with a combination of it('debounces the prop', done => {
wrapper.prop('debounced')('arg1', 'arg2');
wrapper.prop('debounced')('arg3', 'arg4');
wrapper.prop('debounced')('arg5', 'arg6');
setTimeout(() => {
expect(props.debounced.calls.count()).toBe(1);
expect(props.debounced).toHaveBeenCalledWith('arg5', 'arg6');
done();
}, 1000);
}); |
I am having this same issue. Using sinon's fake timers I am able to advance the clock and test that a debounced function is called. I am trying to convert to Jest, and using Jest's fake timers, I get I am able to use real timers and do something similar to @jkaipr above:
|
I was able to get around this by mocking lodash's import debounce from 'lodash/debounce'
import { someFunction, someDebouncedFunction } from '../myModule';
jest.mock('lodash/debounce', () => jest.fn(fn => fn));
...
it('tests something', () => {
debounce.mockClear();
someFunction = jest.fn();
someDebouncedFunction();
expect(debounce).toHaveBeenCalledTimes(1);
expect(someFunction).toHaveBeenCalledTimes(1);
}); |
Since lodash doesn’t use timers for it’s denounce and throttle I assume this particular issue is not actionable from our side and should be closed. |
What do you mean by not using timers? |
That’s fine with me |
What timers does Lodash not use? I'm guessing the issue with jest is it has a problem with mocking For those wanting alternative mocks you might look at sinon.js/lolex. |
@jdalton looks like I misunderstood what you wrote earlier, sorry about that 😅. |
Should we look into just integrating lolex instead of rolling our own fake timer implementation? |
@SimenB That would be rad! |
Opened up #5165 for it. |
I shouldn't have closed this in the first place, so I'm reopening it. |
I had a case where I wanted to test component which used const originalDebounce = _.debounce;
jest.spyOn(_, 'debounce').mockImplementation((f, delay) => {
// can also check if f === yourDebouncedFunction
if (delay === constants.debounceTime) {
return f;
}
return originalDebounce(f, delay);
}); Hope this will help someone |
I'm having the same issue with the 100,000 limit reached and I've found out that the lodash implementation of debounce is creating a ton of timers. @jdalton is that expected that lodash creates so many timers? |
Instead of using jest faketimers that don't seem to work directly, (maybe because jestjs/jest#3465) just mock `_.throttle` to make it return the throttled function directly.
I used this to mock lodash's debounce, it works for most of my use cases: export const debounce = jest.fn().mockImplementation((callback, timeout) => {
let timeoutId = null;
const debounced = jest.fn(()=>{
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(callback, timeout);
});
const cancel = jest.fn(()=>{
window.clearTimeout(timeoutId);
});
debounced.cancel = cancel;
return debounced;
});
export default debounce; then just use it with jest's timer mocking and your tests should behave correctly. as always, extend as appropriate :) |
The <SearchBar /> component debounces searches. Normally that would be tested using Jest's fake timers, however due to this issue jestjs/jest#3465 it's using real timers and a timeout of 500ms. A placeholder prop to be passed onto the <InputText /> was also added.
The <SearchBar /> component debounces searches. Normally that would be tested using Jest's fake timers, however due to this issue jestjs/jest#3465 it's using real timers and a timeout of 500ms. A placeholder prop to be passed onto the <InputText /> was also added.
Thanks @xevrem! But the debounced function is not being passed the correct arguments, I suggest updating your example with this (accept and spread const debounced = jest.fn((...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(...args), timeout);
}); |
Merged a fix 1 day before the issue's 3 year anniversary 😅 Available in |
Got it to work with sinonjs fake timers. Here's a small sample: import FakeTimers from '@sinonjs/fake-timers';
let clock;
describe('some tests', () => {
beforeEach(() => {
clock = FakeTimers.createClock();
});
it('should do a thing', () => {
const fakeWaitInMillis = 5000;
// call func that uses debounce
clock.setTimeout(() => {
// expect func to be called after wait
}, fakeWaitInMillis);
});
}); |
Another basic mock similar to @xevrem answer but with a mocked .flush() as well, in case you need that. Also note if you are just importing the const debounce = jest.fn().mockImplementation((callback, delay) => {
let timer = null;
let pendingArgs = null;
const cancel = jest.fn(() => {
if (timer) {
clearTimeout(timer);
}
timer = null;
pendingArgs = null;
});
const flush = jest.fn(() => {
if (timer) {
callback(...pendingArgs);
cancel();
}
});
const wrapped = (...args) => {
cancel();
pendingArgs = args;
timer = setTimeout(flush, wrapped.delay);
};
wrapped.cancel = cancel;
wrapped.flush = flush;
wrapped.delay = delay;
return wrapped;
});
export default debounce; |
- Fake timers cause an infinite loop on _.debounce. - Jest v26 contains a 'modern' option for useFakeTimers, but create-react-app uses an older version of jest jestjs/jest#3465 (comment) - Mock debounce - Test fails without a dummy call to the database. Why?
Got this error when using Switching to I didn't need lodash mocks or |
I removed the
|
So whats the actual recommended way of dealing with this? |
Modern timers will be the default in |
Thanks for the reply @SimenB, seem to still run into the problem even with Would love to figure it out though as the point at which it fails it throws a nasty error with For anybody wishing to mock it I used @alecklandgraf |
Please open up a new issue with a minimal reproduction if you're stilling having issues |
little addition to @xevrem version here export const debounce = jest.fn().mockImplementation((callback, timeout) => {
let timeoutId = null;
const debounced = jest.fn((...args)=>{
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(...args), timeout);
});
const cancel = jest.fn(()=>{
window.clearTimeout(timeoutId);
});
debounced.cancel = cancel;
return debounced;
});
export default debounce; |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Do you want to request a feature or report a bug?
bug
What is the current behavior?
When using fake timers, creating a debounced function, calling it a couple times, and then calling
jest.runAllTimers
, an error will be printed:It seems that changing the second argument passed to debounce (the time in milliseconds to debounce the function for) changes whether or not this error occurs. For example: on my machine (mid-2014 MBP) it appears to always throw when the delay is above ~600ms, but only fails some of the time when it's around 500ms.
This issue has been encountered before (lodash/lodash#2893), and it seems to have been on Lodash's end. I added a comment to the issue in the Lodash repo, but @jdalton said that he's not sure why it would still be occurring with recent versions of Lodash.
If the current behavior is a bug, please provide the steps to reproduce and either a repl.it demo through https://repl.it/languages/jest or a minimal repository on GitHub that we can
yarn install
andyarn test
.https://github.com/rimunroe/lodash-jest-timer-issue
What is the expected behavior?
The calling
jest.runAllTimers
should cause the debounced function to behave as though the time it was told to debounce for elapsed.Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.
I'm using macOS 10.12.4
No configuration other than calling
jest.runAllTimers
in the test.I encountered the bug with the following versions:
The text was updated successfully, but these errors were encountered: