jestjs / jest

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

[Feature]: Expect the assertion to eventually pass #15202

Open pdonnelly3 opened 2 months ago

pdonnelly3 commented 2 months ago

šŸš€ Feature Proposal

Adding a new expect().eventually() or expectEventually functionality that is similar to vitest's poll and chai-wait-for

It will retry the assertion until it succeeds or hits a number of retries or exceeds an amount of time.

Motivation

Currently, we have our own wrapper that takes a function that contains expect statements and runs them until there are no errors. Our e2e tests have a lot of background jobs that reach out to external services so it's a toss up on when they will finish so just sleeping for a certain amount of time before the expect isn't a viable solution. We also try to avoid retesting entire suites since our tests take a long time to finish and when we switched to our expectEventually wrapper, our test flakiness decreased and our CI runs completed much faster.

There are some other third party packages out there that also provide the same functionality but a lot are no longer being maintained so it would be great if this was built into Jest to make e2e testing simpler.

Example

This is how our current wrapper works which is one way this could be implemented

it('test some background job', async () => {
     await expectEventually(async () => {
        const someValue = await getSomeAsyncValueThatMayNotBeDoneYet();
        expect(someValue).toEqual('I am done');

        const result = await getSomeOtherValueThatUsesThePreviousValueThatAlsoMightNotBeDoneYet(someValue);
        expect(result).toEqual('we are both done');
    });
});

Another way could be

it('test some background job', async () => {
     await expect.eventually(async () => {
        const someValue = await getSomeAsyncValueThatMayNotBeDoneYet();
        expect(someValue).toEqual('I am done');

        const result = await getSomeOtherValueThatUsesThePreviousValueThatAlsoMightNotBeDoneYet(someValue);
        expect(result).toEqual('we are both done');
    });
});

or the a more concise version where eventually returns the value of the provided function so it can be used later

it('test some background job', async () => {
     const someValue = await expect.eventually(async () => getSomeAsyncValueThatMayNotBeDoneYet()).toEqual('i am done');
     const result = await expect.eventually(async () => getSomeOtherValueThatUsesThePreviousValueThatAlsoMightNotBeDoneYet(someValue)).toEqual('we are both done');
});

Pitch

This makes e2e testing with asynchronous functionality, background jobs, and external services that may not be updated yet much easier to write. It also reduces flakiness in tests and improves the overall speed of the tests since only the failed expect statements get re-ran.

github-actions[bot] commented 1 month 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.

nidomiro commented 3 weeks ago

I would also like this, since I already implemented something similar and don't want to test async code without it anymore. No more random delay numbers which "work on my machine"ā„¢ļø