-
-
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
[jest-circus] Support both done callback and async test simultaneously #10529
Comments
+1, this prevents |
I've been snoozing this issue for weeks meaning to get back to it 😛 The OP was very detailed and well thought out, so I wanted to let it simmer for a bit. I don't wanna rollback the change as I think it's a good one - tests are way easier to reason about when there's not multiple async paradigms in use at the same time. We've also been warning for at least a full major version. To concretely come up with a working test for the example in the OP I'd do something like this import { once } from 'events';
test('intended workflow', async () => {
const connectedPromise = once(connection, 'connected');
await Promise.all([connection.connect(), connectedPromise]);
expect(connection.isConnected()).toBeTruthy();
});
Would be even better if events were guaranteed to be emitted on next tick, but that's not the way node event emitters work, thus the import { once } from 'events';
test('intended workflow', async () => {
await Promise.all([connection.connect(), once(connection, 'connected')]);
expect(connection.isConnected()).toBeTruthy();
}); All that to say is that I think the change is a good one - APIs mixing callbacks and promises are weird and hard to reason about, and coercing them to follow a single paradigm (by wrapping the APIs or using helpers like I've done in this post) makes the code cleaner and easier to reason about. /cc @jeysal @thymikee @cpojer would love your thoughts on this |
The But yeah, I reiterate:
Not even really Jest's fault either, JS just doesn't provide a lot of utility for writing robust, non-trivial async workflows. But it'd be good if Jest could help pave a cowpath so it was easier to write clean, robust, async tests.
Agreed but unfortunately fairly common to have multiple paradigms in play and may be unavoidable in many codebases. Ideally it wouldn't be so perilous to do so. |
BREAKING CHANGE - Node 10 won't work due to jsdom bug, see jestjs/jest#10957. If you have errors with node 10 related to `globalThis`, workaround for now is switching to node 12. - Since default `testRunner` in Jest 27 is `jest-circus`, `async` test with `done` callback no longer works, see discussion at jestjs/jest#10529. If you want to have `async` test with `done` callback, please use `testRunner: 'jest-jasmine2'` in your jest config.
Any progress on this? I reaaly wanted to switch to jest-circus for razzle but this is a blocker for that. |
….1.0 feat(2151): helm-release-v12.1.0 - Updated dependencies - Fixes for updated dependencies - Bump patch version - Fixed lint issues - Added dep:check/update scripts, and made update:check/update aliases - Fixed unit & integration tests - some refactoring required as using "async-await" & "done-callbacks" are not compatible --> jestjs/jest#10529 - Added fix for 'SDK Scheme Adapter is not accepting PUT /parties with 1.1 content-type header': mojaloop/project#1891
Definitely e deal breaker for transitioning from the |
At least until jestjs/jest#10529 lands.
…s and unnecessary with async functions, was reported as error by jest-circus e.g. jestjs/jest#10529)
* use circus runner for integration tests * do not use done callback. jestjs/jest#10529 * fix type error
* use circus runner for integration tests * do not use done callback. jestjs/jest#10529 * fix type error
…102948) * [jest] use circus runner for the integration tests (#102782) * use circus runner for integration tests * do not use done callback. jestjs/jest#10529 * fix type error * disable reporting for so 100k migration Co-authored-by: Mikhail Shustov <[email protected]>
* chore(deps): update dependency jest to v27 * chore(deps): update ts-jest to v27.0.3 * chore(jest): set `jsdom` as `testEnvironment` in config * test: do not use `done()` callback (jestjs/jest#10529) Co-authored-by: Renovate Bot <[email protected]> Co-authored-by: Pablo Vinícius <[email protected]>
I’m surprised this is being called for. For me, the Writing this not to raise arguments, but to support @SimenB ’s write from Dec 2020. |
This comment has been minimized.
This comment has been minimized.
From https://jestjs.io/docs/asynchronous should be updated to point this out, while it doesn't say you can mix and match within a single test, it should clearly state that these 2 methods of testing are incompatible. |
Here's a workaround for now. I don't know if there's any gotcha's, I assume there are some, if it was that simple jest would've already supported it. function itAsyncDone(
name: string,
cb: (done: jest.DoneCallback) => Promise<void>,
timeout?: number,
) {
it(
name,
(done) => {
let doneCalled = false;
const wrappedDone: jest.DoneCallback = (...args) => {
if (doneCalled) {
return;
}
doneCalled = true;
done(...args);
};
wrappedDone.fail = (err) => {
if (doneCalled) {
return;
}
doneCalled = true;
done.fail(err);
};
cb(wrappedDone).catch(wrappedDone);
},
timeout,
);
} |
Hi @masad-frost, thank you so much for your workaround. We are migrating from Jasmine to Jest lots of projects that use both done callback and async functions so your method is making our lifes easier. I modified it with two changes:
Thank you so much, greetings. |
+1 This is breaking every test where I'm testing thrown errors. The methods I'm running are EDIT TO ADD: For my case, chaining a |
tried running done inside a .catch() instead of async await , but didn't work also |
My tests now look pretty silly because I can not use it("should be able subscribe to container set change", (cb) => {
// This is silly
;(async () => {
const cont = getMainMockAppContainer()
let containerSet = await cont.getContainerSet(["aCont", "bCont", "cCont"]) // await improves readability
expect(containerSet.bCont.b2).toMatchObject({ a1: {} })
expect(containerSet.cCont.c2.size).toBe(5)
cont.subscribeToContinerSet(["aCont", "bCont", "cCont"], (containerSet) => {
expect(containerSet.cCont.c2.size).toBe(10)
cb()
})
containerSet.cCont.upgradeCContainer()
})()
}) I would rather do this it("should be able subscribe to container set change", async (cb) => {
const cont = getMainMockAppContainer()
let containerSet = await cont.getContainerSet(["aCont", "bCont", "cCont"])
expect(containerSet.bCont.b2).toMatchObject({ a1: {} })
expect(containerSet.cCont.c2.size).toBe(5)
cont.subscribeToContinerSet(["aCont", "bCont", "cCont"], (containerSet) => {
expect(containerSet.cCont.c2.size).toBe(10)
cb()
})
containerSet.cCont.upgradeCContainer()
}) |
Also, this is a breaking change, because it worked until jest 25 or something and I am sure I have hundreds of spec files written that way |
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days. |
I think this issue is still valid and we have interest in it. I'd either like to see this regression fixed or some clearer guidance on why these two async test strategies should not be combined. |
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days. |
bump |
This should probably be closed as there are workarounds, and the team is confident in the decision. Most of the examples from people mentioned in this thread are callbacks that can simply be wrapped in a promise, but here's an example where I think the oversight lies and leads to unergonomic, hard to read, and hard to write tests. test("something", async (done) => {
onShouldNeverHappen(() => done(new Error("should never happen")));
const bar = await foo();
expect(bar).toBe(1);
done()
}); Obviously, you can do something like test("something", async () => {
const shouldNeverHappenFn = jest.fn();
onShouldNeverHappen(shouldNeverHappenFn);
const bar = await foo();
expect(shouldNeverHappenFn).not.haveBeenCalled()
expect(bar).toBe(1);
}); But it becomes really repetitive when you have multiple async things happening where you have to check that your callback is not called after each one and/or multiple callbacks that you expect not to be called. What's worse is that test("something", async () => {
const result = await Promise.race([
foo(),
new Promise((resolve, reject) => {
onShouldNeverHappen(() => {
reject(new Error("Should never happen function was called"));
});
})
]);
expect(result).toBe(1);
}); Hopefully I'm missing something obvious here. |
@masad-frost This doesn't work in vitest so well too :/ |
🚀 Feature Proposal
Please support writing async tests that can also use the
done
callback.This didn't work "properly" before the new
jest-circus
runner forbid this combination, but it did sorta work enough to be useful. #9129Ideally, an
async (done) => {}
test would not complete successfully unless both:done
callback was called without error ANDTest would fail immediately if one of:
done
callback is invoked with a truthy (error) value ORPromise
rejects ORWhen to proceed to after hooks and next test is debatable:
done
errors, should it wait for the promise to settle or timeout occurs?done
is called, should it wait fordone
to be called or timeout occurs?IMO it should always wait for promise to settle/timeout, but not wait for
done
if the promise rejects.Motivation
Currently:
With the
done
ORPromise
setup, how do you set up a test that checks both that a promise resolved AND an event was fired? Seems like this should be simple, but is it?Consider a
connection
object , something like:How to test the behaviour of this?
Here's some examples of partial/naive solutions to testing this that I have seen in the wild (or done myself).
For example, this doesn't verify that the promise resolved, or didn't reject before moving onto the next test:
And conversely using an
async
test, we can't check that the event was fired before the test ended:One way to set it up that will work, and handle all cases, is to queue the promise and then resolve the promise with done:
But IIUC with the new
jest-circus
runner (at least in version 26.4.2), if that expect call fails, then test will wait to time out before moving on to the next test becausedone
is never called becauseexpect
threw. This isn't ideal if we want fast tests. So I guess we have to wrap every event handler in a try/catch?Perhaps that's off-topic. update: I've opened a ticket about uncaught exception waiting for timeout here #10530
The
done
callback style test doesn't give us the convenience ofawait
though.To set it up with an
async
test you have to do something like the code below:However the above does change the task timing of when the 'connected' event's expect call is run, it no longer runs in the same microtask as the event, so to restore this timing you have to do something like:
However on failure, this will never call the
reject
/resolve
so the test will also time out. Perhaps we wrap theexpect
in a try/catch?Overall this is all a lot of thinking and messing around in order to get robust tests for something that could be simple.
Example
Ideally the Promise + done test would work something like so:
Test would pass once both done and the returned promise resolve, one of them rejects, or the test times out.
And the test runner would do something like this (pseudocode):
Could also consider using
Promise.allSettled
instead ofPromise.all
in order to wait for both done + resolve/reject before continuing, but that would forces the test to hit timeout in a case likeexpect
throwing inside an event handler leading todone
not being called? Or perhaps, when an unhandled exception/rejection fires, this implicitly callsdone
, so it doesn't have to wait?Another option would be to use
expect.assertions(n)
to signal the test's end, but this is a PITA if you haveexpect
assertions in before/after hooks, as these are included in the overall count for every test affected by those hooks.edit: Perhaps the correct answer in this case is to simply test events and promises separately, which requires no changes and is arguably cleaner?
The text was updated successfully, but these errors were encountered: