jestjs / jest

Delightful JavaScript Testing.
https://jestjs.io
MIT License
44.19k stars 6.46k forks source link

[Feature]: API for obtaining a deadline AbortSignal in tests #14489

Closed panayot-cankov closed 10 months ago

panayot-cankov commented 1 year ago

šŸš€ Feature Proposal

Provide a test deadline (and test lifecycle hooks) of type AbortSignal. Jest should give chance for async tests to reject promises, when the AbortSignal is aborted, so jest can capture proper stack traces on timeout.

Motivation

I need to cancel some asynchronous operations in tests when tests timeout. AbortSignals provide events for that purpose. They also capture good stack traces with the test progress during the abort.

Example

Node 18 supports experimental AbortControllers and AbortSignals. What I am looking for is something like:

describe("My web API", () => {
  test("Option 1", async () => {
    const signal = expect.deadline; // <-- The new API.
    const token = await fetch("API1", { signal });
    const token = await fetch("API2", { signal });
    const token = await fetch("API3", { signal });
  }, 5000);
});

The deadline is an AbortSignal that is aborted by jest when the test timeouts. The deadline.reason should en Error familiar to jest, that the underlying fetch calls could use to throw an Error. Jest should wait for the promises to bubble the error up until the test function throws. At that point the error should have a meaningful stacks trace. E.g. if the API2 took too long, that line should be in the Error stack trace. And if the signal is not used in all async actions in the test and such error is not captured shortly after jest do AbortController.abort(new TestTimeoutError(... then the test execution should continue as it currently does. When test throws, the actual error could be nested (see errors with causes)

The expect.deadline AbortSignal for the test timeout should be available in "beforeEach" callbacks. There may be other properties like expect.beforeEachDeadline, expect.beforeAllDeadline, expect.afterEachDeadline, expect.afterAllDeadline, but the test deadline signal could be used in before each, to wire an object graph with testing APIs, like passing it to webdriver, that on timeouts would actually show proper stack traces with the test progress:

descrive("web tests", () => {
  let driver;
  beforeAll("setup web driver", async () => {
    await driver = await WebDriver.build();
  });
  beforeEach("setup the test deadline", async () => {
    driver.deadline = expect.deadline;
  });
  test("my test", async () => {
    await driver.findElement(By.css("my.button")).click();
    await driver.findElement(By.css("your.button")).click(); // If the tests timeouts here, show this line as part of the call stack, because the driver will use the deadline internally to pass to fetch
  });
});

The deadline could be provided as this.deadline if tests are supposed to run in parallel:

describe("My web API", () => {
  test("Option 1", async function() {
    const signal = this.deadline; // <-- The new API.
    const token = await fetch("API1", { signal });
    const token = await fetch("API2", { signal });
    const token = await fetch("API3", { signal });
  }, 5000);
});

Pitch

test("my test", async () => {
    const signal = this.deadline; // <-- The new API.
    const token = await fetch("http://localhost/api1", { signal });
    const token = await fetch("http://localhost/api2", { signal });
    // If the tests timeouts here, show this line as part of the call stack in the test results!
    const token = await fetch("http://localhost/api3", { signal });
});
github-actions[bot] commented 1 year ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.

panayot-cankov commented 1 year ago

I've been experimenting with a custom jest environment. This seems to work somewhat well: https://github.com/telerik/roadkill/blob/67e2f617aff43d0c7c5625e0cf3bac2e89ed1de8/packages/%40progress/roadkill/jest-environment.ts#L514 The environment will setTimeout with the exact same duration of the test and hook, and the signals will abort promise chains and display errors with stack.

github-actions[bot] commented 11 months ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.

github-actions[bot] commented 10 months ago

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

github-actions[bot] commented 10 months ago

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

github-actions[bot] commented 9 months ago

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. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.