Skip to content

Instantly share code, notes, and snippets.

@felipepodesta
Created June 1, 2022 13:41
Show Gist options
  • Save felipepodesta/11a2e9aa83e992258e5e0eed89e3d20c to your computer and use it in GitHub Desktop.
Save felipepodesta/11a2e9aa83e992258e5e0eed89e3d20c to your computer and use it in GitHub Desktop.

Revisions

  1. @VinceOPS VinceOPS revised this gist Apr 29, 2019. 2 changed files with 24 additions and 2 deletions.
    22 changes: 22 additions & 0 deletions wait-for-assertion.spec.ts
    Original file line number Diff line number Diff line change
    @@ -51,4 +51,26 @@ describe('waitForAssertion', () => {
    expect(e).toBeInstanceOf(TimeoutError);
    }
    });

    it('throws forward instances of TypeError', async () => {
    try {
    await waitForAssertion(() => {
    const failedInjectionService: any = {};
    failedInjectionService.foo();
    });
    } catch (e) {
    expect(e).toBeInstanceOf(TypeError);
    }
    });

    it('throws forward instances of ReferenceError', async () => {
    try {
    await waitForAssertion(() => {
    // @ts-ignore
    b.foo();
    });
    } catch (e) {
    expect(e).toBeInstanceOf(ReferenceError);
    }
    });
    });
    4 changes: 2 additions & 2 deletions wait-for-assertion.ts
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import { from, interval } from 'rxjs';
    import { from, interval, throwError } from 'rxjs';
    import { catchError, first, switchMap, timeout } from 'rxjs/operators';

    /**
    @@ -24,7 +24,7 @@ export function waitForAssertion(assertion: () => any, timeoutDelay: number = 10
    return interval(intervalDelay)
    .pipe(
    switchMap(() => from(Promise.resolve(assertion()))),
    catchError((err, o) => o),
    catchError((err, o) => (err instanceof ReferenceError || err instanceof TypeError ? throwError(err) : o)),
    first(),
    timeout(timeoutDelay),
    )
  2. @VinceOPS VinceOPS created this gist Dec 10, 2018.
    54 changes: 54 additions & 0 deletions wait-for-assertion.spec.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,54 @@
    import { TimeoutError } from 'rxjs';
    import { waitForAssertion } from './wait-for-assertion';

    describe('waitForAssertion', () => {
    let mockWitness: jest.Mock;

    beforeEach(() => {
    mockWitness = jest.fn().mockReturnValue(false);
    });

    describe('Waits both synchronous and asynchronous callbacks', () => {
    let intervalsCount: number;
    let intervalDelay: number;
    let delayBeforeSuccessTrigger: number;
    let timeoutDelay: number;

    beforeEach(() => {
    intervalsCount = 4;
    intervalDelay = 20;
    delayBeforeSuccessTrigger = (intervalsCount + 1) * intervalDelay;
    // safety margin: use more than the expected execution time as timeout delay
    timeoutDelay = 3 * intervalsCount * intervalDelay;
    });

    it('waits for an asynchronous assertion to succeed', async () => {
    setTimeout(() => mockWitness.mockReturnValue(true), delayBeforeSuccessTrigger);

    await waitForAssertion(() => expect(mockWitness()).toBe(true), timeoutDelay, intervalDelay);
    // "success" is triggered after `intervalsCount` + 1 cycles, so expect at least `intervalCount` calls
    expect(mockWitness.mock.calls.length).toBeGreaterThanOrEqual(intervalsCount);
    });

    it('also works with async closures', async () => {
    setTimeout(() => mockWitness.mockResolvedValue(true), delayBeforeSuccessTrigger);

    await waitForAssertion(() => expect(mockWitness()).resolves.toEqual(true), timeoutDelay, intervalDelay);
    expect(mockWitness.mock.calls.length).toBeGreaterThanOrEqual(intervalsCount);
    });
    });

    it('throws a TimeoutError if the assertion did not succeed', async () => {
    const expectedCyclesCount = 4;
    const intervalDelay = 100;
    const timeoutDelay = (expectedCyclesCount + 1) * intervalDelay;

    expect.assertions(expectedCyclesCount + 1);

    try {
    await waitForAssertion(() => expect(mockWitness()).toBe(true), timeoutDelay, intervalDelay);
    } catch (e) {
    expect(e).toBeInstanceOf(TimeoutError);
    }
    });
    });
    32 changes: 32 additions & 0 deletions wait-for-assertion.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,32 @@
    import { from, interval } from 'rxjs';
    import { catchError, first, switchMap, timeout } from 'rxjs/operators';

    /**
    * Wait for any assertion or group of assertions to succeed, or timeout.
    *
    * Run (through a promise) the given function `assertion` every `intervalDelay` milliseconds
    * until it stops throwing or until `timeoutDelay` is passed (timeout).
    *
    * @param assertion Closure containing all assertions to be made (calls of `expect()`).
    * @param timeoutDelay How long should the assertion be repeated until it passes (or times out).
    * @param intervalDelay How often should the assertion be repeated during `timeoutDelay`.
    *
    * @return Resolve on success, or reject with a `TimeoutError`.
    *
    * @example
    * // in a test, where we need to ensure a value is asynchronously updated in elasticsearch
    * await waitForAssertion(async () => {
    * const { document } = await elasticsearchService.get(UserIndex, userId);
    * return expect(document.firstName).toBe(updatedUser.firstName);
    * });
    */
    export function waitForAssertion(assertion: () => any, timeoutDelay: number = 1000, intervalDelay: number = 100) {
    return interval(intervalDelay)
    .pipe(
    switchMap(() => from(Promise.resolve(assertion()))),
    catchError((err, o) => o),
    first(),
    timeout(timeoutDelay),
    )
    .toPromise();
    }