Description
@testing-library/dom
version: 7.26.3 (but I also reproduce in 7.27.0)- Testing Framework and version: Jest 26.6.3
- DOM Environment: jsdom 16.4.0
- node 12.18
Relevant code or config:
See below.
What you did:
Simply using findByText
in some promise-based code while using fake timers, working on a new test.
What happened:
This is the error I get:
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "A function to advance timers was called but the timers API is not mocked with fake timers. Call `jest.useFakeTimers()` in this test or enable fake timers globally by setting `"timers": "fake"` in the configuration file. This warning is likely a result of a default configuration change in Jest 15.
Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15.html
Stack Trace:
Error:
at FakeTimers._checkFakeTimers (node_modules/@jest/fake-timers/build/legacyFakeTimers.js:436:13)
at FakeTimers.advanceTimersByTime (node_modules/@jest/fake-timers/build/legacyFakeTimers.js:263:10)
at node_modules/@testing-library/dom/dist/wait-for.js:60:14".
at BufferedConsole.warn (node_modules/@jest/console/build/BufferedConsole.js:242:10)
at FakeTimers._checkFakeTimers (node_modules/@jest/fake-timers/build/legacyFakeTimers.js:426:28)
at FakeTimers.advanceTimersByTime (node_modules/@jest/fake-timers/build/legacyFakeTimers.js:263:10)
at node_modules/@testing-library/dom/dist/wait-for.js:60:14
One main issue is that the error is repeated indefinitely, I have to ctrl-c to stop the runner. It also obscures the real error happening before.
Reproduction:
This is the simplest code showing the problem:
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('errors with a weird error', () => {
// wait forever
waitFor(() => {
throw Error();
});
});
Obviously in this code I forgot to "await" waitFor
, as a result the test stops before waitFor ends.
An example closer to the real life is this:
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('errors with a weird error', async () => {
Promise.resolve().then(() => {
throw Error('oops, error');
});
// wait forever
await waitFor(() => {
throw Error();
});
});
The problem is that the test errors before waitFor finishes, fake timers are torn down automatically, but the code in waitFor still wants to use the fake timers.
I couldn't make it fail on codesandbox, I believe because setImmediate
is aliased to setTimeout
in browsers, but here is a repository where I reproduce locally: https://github.com/julienw/jest-dom-library-issue
git clone https://github.com/julienw/jest-dom-library-issue
cd jest-dom-library-issue
yarn
yarn test
Suggested solution:
I believe that the most important issue is that this never stops. The timeout should set finished
to false... but I think that the timeout never runs because the loop is too "tight" (it's using setImmediate
, maybe it's using a microtask, but couldn't find anything conclusive about that -- but probably it has precedence over setTimeout
).
Note: I could live with one such log that would let me see easily the real error.
Other ideas:
- waitFor should know that the test stopped and stop right away. Is it possible to get notified about that? Should we register an
beforeEach
andafterEach
callbacks to get notified of when a test starts and stops? - we check if fake timers are enabled in https://github.com/testing-library/dom-testing-library/blob/master/src/wait-for.js#L54, and then use it all the time in https://github.com/testing-library/dom-testing-library/blob/master/src/wait-for.js#L68, maybe we should check it again at each loop and bail out if this changed? Would that be a perf problem?
But again I could live with these 2 problems if this wasn't looping forever.