Jest: Proporcionar una API para vaciar la cola de resolución de promesas

Creado en 23 nov. 2016  ·  46Comentarios  ·  Fuente: facebook/jest

¿Quieres solicitar una función o informar de un error ?

_Feature_, supongo, pero muy importante cuando se prueba código que usa Promise s.

¿Cuál es el comportamiento actual?

Tengo un componente que usa Promise envolviendo y encadenando internamente en el seguimiento de una acción asincrónica externa. Estoy proporcionando la simulación de la acción asíncrona y resolviendo la promesa que devuelve en mi prueba.

El componente algo como esto:

class Component extends React.Component {
  // ...
  load() {
    Promise.resolve(this.props.load())
      .then(
        result => result
          ? result
          : Promise.reject(/* ... */)
        () => Promise.reject(/* ... */)
      )
      .then(result => this.props.afterLoad(result));
  }
}

Y el código de prueba se parece a esto:

const load = jest.fn(() => new Promise(succeed => load.succeed = succeed));
const afterLoad = jest.fn();
const result = 'mock result';
mount(<Component load={load} afterLoad={afterLoad} />);
// ... some interaction that requires the `load`
load.succeed(result);
expect(afterLoad).toHaveBeenCalledWith(result);

La prueba falla porque expect() se evalúa antes que los manejadores de promesas encadenadas. Tengo que replicar la longitud de la cadena de promesa interna en la prueba para obtener lo que necesito, así:

return Promise.resolve(load.succeed(result))
  // length of the `.then()` chain needs to be at least as long as in the tested code
  .then(() => {})
  .then(() => expect(result).toHaveBeenCalledWith(result));

¿Cuál es el comportamiento esperado?

Esperaría que Jest proporcione algún tipo de API para eliminar todos los manejadores de promesas pendientes, por ejemplo:

load.succeed(result);
jest.flushAllPromises();
expect(result).toHaveBeenCalledWith(result);

He probado runAllTicks y runAllTimers sin ningún efecto.


_Alternativamente, si simplemente me falta alguna característica o patrón ya existente, espero que alguien aquí me indique la dirección correcta :) _

Enhancement New API proposal

Comentario más útil

Una función auxiliar puede convertir eso en una promesa en sí misma, por lo que no necesita lidiar con la devolución de llamada realizada. Es lo suficientemente pequeño, es bastante inofensivo para mantenerlo en la tierra de los usuarios, pero no me quejaría si se pusiera en el objeto de broma. Algo así se usa mucho en mis proyectos.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

Con async await es casi bonito:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

Todos 46 comentarios

Si bien las pruebas asíncronas prometen, es bueno recordar que puede devolver una función de prueba como una promesa, por lo que algo como esto funcionará:

test('my promise test', () => { //a test function returning a Promise
  return Promise.resolve(load.succeed(result))
    .then(() => {})
    .then(() => expect(result).toHaveBeenCalledWith(result));
})

Devolver una Promesa de la función de prueba hace que Jest sea consciente de que se trata de una prueba asíncrona y debe esperar hasta que se resuelva o se agote el tiempo de espera.

@thymikee Por supuesto, estoy devolviendo el valor para hacer esperar a Jest, eso está completamente fuera de lugar. Tenga en cuenta que incluso dejó la línea .then(() => {}) en su código. No veo cómo puedo describir el problema de manera más concisa de lo que ya lo hice en la publicación inicial. Léalo detenidamente y vuelva a abrir el problema o describa cómo solucionarlo.

_He agregado el return al código en el OP para evitar confusiones._

Me encontré con un problema similar y lo describí aquí: https://github.com/pekala/test-problem-example

En resumen: estoy tratando de afirmar la secuencia de acciones enviadas a la tienda Redux como resultado de la interacción del usuario (simulada con enzima). Las acciones como sincronizadas y asincrónicas enviadas usando Promises (burladas para resolver de inmediato). Parece que no hay forma de afirmar después de que se agota la cadena de promesas, si no tiene acceso directo a la cadena de promesas. setTimeout(..., 0) funciona, pero se siente mal y si la aserción en la devolución de llamada de setTimeout falla, Jest falla con un error de tiempo de espera (en lugar de un error de aserción).

La idea de flushAllPromises parece una solución, aunque creo que eso es lo que debería hacer runAllTicks .

Como seguimiento: Intenté reemplazar setTimeout(..., 0) con setImmediate y esto parece ejecutar las aserciones después de que la cola de microtask de devolución de llamada de Promise se agote y evita que Jest agote el tiempo de espera en los errores de aserción. Entonces, esto funciona bien y es una solución aceptable para mi caso de uso:

test('changing the reddit downloads posts', done => {
    setImmediate(() => {
        // assertions...
        done()
    })
})

Una función auxiliar puede convertir eso en una promesa en sí misma, por lo que no necesita lidiar con la devolución de llamada realizada. Es lo suficientemente pequeño, es bastante inofensivo para mantenerlo en la tierra de los usuarios, pero no me quejaría si se pusiera en el objeto de broma. Algo así se usa mucho en mis proyectos.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

Con async await es casi bonito:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

@jwbay ¡ eso es un buen azúcar allí mismo 🍠!

Es cierto que este flushPromises resulta ser una sola línea, pero no es del todo obvio cómo llegar a esa línea. Así que creo que sería beneficioso para los usuarios de Jest tenerlo disponible como una función útil.

@pekala the one liner IMO no proporciona el comportamiento requerido porque no esperará hasta que se resuelva la siguiente promesa pendiente:

function foo() {  
  return new Promise((res) => {
    setTimeout(() => {
      res()
    }, 2000);
  });
}

¿Qué pasa con Swizzling Promise y cuando se crea una nueva Promise, agréguela a alguna matriz y luego elimine todas las promesas que esperan en Promise.todas las de esta matriz?

@talkol Creo que lo hará, siempre y cuando ustedes también sean los temporizadores falsos. Aunque no lo he probado.

@pekala no es necesario falsificar temporizadores con este ejemplo, ya que la promesa se resolverá solo después de que se alcance el tiempo
Solo me preocupa que Promise swizzling se meta con el funcionamiento interno de la broma, es un poco duro

Si no falsifica temporizadores, sus pruebas tardarán más de 2 segundos en completarse. Creo que la mejor práctica sería eliminar este tipo de retrasos, en cuyo caso el flushPromises propuesto por @jwbay hace el trabajo.

Todo depende de lo que intente probar :) Todo lo que digo es que los temporizadores son una preocupación no relacionada con la espera de las promesas

Estamos enfrentando problemas relacionados con promesas que no se resuelven, que se entremezclan con llamadas setTimeout. En jest v19.0.2 no tenemos problemas, pero en jest v20.0.0 Promises nunca ingresan a las funciones de resolver / rechazar y por lo tanto las pruebas fallan. Nuestro problema parece estar relacionado con este problema de no tener _una API para vaciar la cola de resolución de Promise_, pero este problema parece ser anterior a la broma v20.0.0 donde comenzamos a ver el problema, así que no estoy completamente seguro.

Esta es la única solución que hemos podido encontrar para algunas de nuestras pruebas, ya que tenemos una serie de setTimeout sy Promise s alternados utilizados en el código que eventualmente llama al onUpdateFailed devolución

  ReactTestUtils.Simulate.submit(form);
  return Promise.resolve()
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => {
      expect(onUpdateFailed).toHaveBeenCalledTimes(1);
      expect(getErrorMessage(page)).toEqual('Input is invalid.');
    });

No es tan bonito, por lo que cualquier consejo aquí es muy apreciado.

Otro ejemplo en el que no puede devolver la promesa de la prueba:

describe('stream from promise', () => {
  it('should wait till promise resolves', () => {
    const stream = Observable.fromPromise(Promise.resolve('foo'));
    const results = [];
    stream.subscribe(data => { results.push(data); });
    jest.runAllTimers();
    expect(results).toEqual(['foo']);
  });
});

Esta prueba falla con la broma 20.0.4.

La solución de @philwhln también se puede escribir con async / await

ReactTestUtils.Simulate.submit(form);

await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();

expect(onUpdateFailed).toHaveBeenCalledTimes(1);
expect(getErrorMessage(page)).toEqual('Input is invalid.');

Me encantaría una función de utilidad que limpiara la cola de promesas

Me encantaría una función que también vacíe las colas de promesas entre pruebas.

Estoy probando código que usa Promise.all para envolver múltiples promesas. Cuando una de esas promesas envueltas falla (porque eso es lo que quiero probar), la promesa regresa inmediatamente, lo que significa que las otras promesas a veces (condición de carrera, no determinista) regresan mientras se ejecuta la siguiente prueba.

Esto causa todo tipo de estragos con mis pruebas que tienen resultados no predecibles / repetibles.

Para implementar esto correctamente, necesitaríamos simular Promise para que eventualmente podamos ver todas las micro tareas en cola para resolverlas sincrónicamente. Algo en el camino de lo que está haciendo la simulación-promesa .

Ya existe una API para eliminar las micro tareas en cola con process.nextTick y esa API probablemente también debería funcionar con Promises ( jest.runAllTicks ).

Tuve una solución con jazmín que se conectó al nextTick of Yaku, una biblioteca de promesas y capté las llamadas de nextTick y permití reproducirlas temprano.
Sin embargo, jest usa promesas en sí mismas, lo que hace que esto sea problemático.
Al final, tomé Yaku y lo pirateé para tener un método de descarga que elimina su cola. De forma predeterminada, se ejecuta normalmente con nextTick, pero si llama a flush, se ejecutan todos los manejadores de promesas pendientes.
La fuente está aquí:
https://github.com/lukeapage/yaku-mock
Le vendría bien poner en orden, ponerse en contacto con ysmood para ver qué piensan de él y agregar documentación, pero prácticamente hace lo que quieres y funcionó para mí como una solución simple para hacer que las promesas se sincronicen en las pruebas.

Como una simple solución a eso, me gusta la solución de @jwbay .

¿Qué tal si agregamos algo similar al objeto jest ?

await jest.nextTick();

Implementado como

const nextTick = () => new Promise(res => process.nextTick(res));

cc @cpojer @SimenB @rogeliog

Estoy usando enzima para montar componentes de React.

Yo también tengo funciones que esperan que se ejecute Promises, pero ninguna de las correcciones mencionadas anteriormente funcionó. Podría manejarlos sincrónicamente en mi prueba - si - las funciones devolvieron los objetos Promise, usando await , pero desafortunadamente las funciones no devuelven los objetos Promise.

Esta es la solución que terminé usando un espía en la función Promesa global.

global.Promise = require.requireActual('promise');

it('my test', async () => {
    const spy = sinon.spy(global, 'Promise');

    wrapper.props().dispatch(functionWithPromiseCalls());

    for (let i = 0; i < spy.callCount; i += 1) {
      const promise = spy.getCall(i);
      await promise.returnValue;
    }

    expect(...)
});

Encontré un caso de uso para esto (gracias @jwbay por la increíble técnica)

Por ejemplo, desea verificar que su función tenga un tiempo de espera y que el tiempo de espera se aplique con precisión:

      jest.useFakeTimers();
      const EXPECTED_DEFAULT_TIMEOUT_MS = 10000;

      const catchHandler = jest.fn().mockImplementationOnce(err => {
        expect(err).not.toBeNull();
        expect(err.message).toContain('timeout');
      });

      // launch the async func returning a promise
      fetchStuffWithTimeout().catch(catchHandler);

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(EXPECTED_DEFAULT_TIMEOUT_MS - 1);
      await flushPromises();

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(1);
      await flushPromises();

      expect(catchHandler).toHaveBeenCalledTimes(1); // ok, rejected precisely

devolver una promesa no permite verificar el momento preciso de la resolución / rechazo.

Allí se necesita un lavado de promesa. Sin él, la expectativa se llama demasiado pronto.

Espero que esto ayude a reducir el problema.

Para las personas que lo siguen, hay un PR abierto para esto aquí: # 6876

Publicación cruzada de https://github.com/airbnb/enzyme/issues/1587

Me pregunto si el siguiente patrón debería ser suficiente para resolver este problema y si estoy haciendo algo que se considera una mala práctica y no debería estar haciendo.

¿Qué piensa la gente sobre este enfoque?

export class MyComponent extends React.Component {
  constructor (props) {
    super(props)

    this.hasFinishedAsync = new Promise((resolve, reject) => {
      this.finishedAsyncResolve = resolve
    })
  }

  componentDidMount () {
    this.doSomethingAsync()
  }

  async doSomethingAsync () {
    try {
      actuallyDoAsync()
      this.props.callback()
      this.finishedAsyncResolve('success')
    } catch (error) {
      this.props.callback()
      this.finishedAsyncResolve('error')
    }
  }

  // the rest of the component
}

Y en las pruebas:

it(`should properly await for async code to finish`, () => {
  const mockCallback = jest.fn()
  const wrapper = shallow(<MyComponent callback={mockCallback}/>)

  expect(mockCallback.mock.calls.length).toBe(0)

  await wrapper.instance().hasFinishedAsync

  expect(mockCallback.mock.calls.length).toBe(1)
})

Tuve un problema cuando la llamada asíncrona no se realizó directamente en componentDidMount, pero estaba llamando a una función asíncrona, que estaba llamando a otra función asíncrona y así sucesivamente. Si agregué un paso asíncrono adicional en toda la cadena asíncrona, necesitaría agregar un .then() adicional o un await adicional, pero esto funciona bien.

¿Hay alguna razón por la que no debería utilizar este enfoque o le parece bien a la gente?

Me embarqué en una aventura al hacer esto en la tierra de los usuarios y descubrí que en realidad es factible y no tan malo (aunque hay bastantes trampas con las que se puede encontrar si no tiene un mapa). Aquí hay un informe de experiencia que (con suerte) es lo suficientemente detallado como para usarlo directamente ; un TLDR es transpilar async / await hasta las promesas e intercambiar promesas nativas por bluebird y temporizadores nativos por lolex; transpilar todo , incluido node_modules/ ; queueMicrotask es la primitiva que necesita para las promesas, pero por defecto lolex no la proporcionará debido a que JSDOM no la proporciona.

Me encontré con el mismo problema con los componentes jest.mockAllTimers() y React que llaman a Promise en componentDidMount() .

La solución de # issuecomment-279171856 resolvió el problema de una manera elegante.

¡Necesitamos algo similar en la API oficial de Jest!

Recientemente me encontré con un problema al actualizar un montón de cosas, reveló un problema en un montón de pruebas en las que no siempre estábamos esperando a que terminaran las promesas. Y aunque métodos como await new Promise(resolve => setImmediate(resolve)); funcionaron en casos simples, descubrí en mis pruebas que tendría que ejecutarlo varias veces para limpiar la tubería. Que es lo que @quasicomputational mencionó en su exploración aquí . Desafortunadamente, no creo que haya una manera de saber cuándo esa tubería está clara sin interceptar las promesas a medida que se crean. Así que creé una pequeña biblioteca para hacer eso ... promesa-espía . Sin embargo, tuve una prueba que usaba temporizadores falsos y no funcionó con eso ... por lo que aún no es una solución completamente funcional.

Aunque también me imagino que solo pueden funcionar con async / await alls en su código para ser probados SI se traducen en promesas. Si no se convierten en promesas, esta biblioteca no podrá conectarse a ellas y esperar a que se completen.

Me encontré teniendo este mismo problema y me di cuenta:
no deberíamos eliminar las promesas pendientes, sino que deberíamos hacer que toda la prueba falle si hay promesas pendientes.
De esta manera, nos veremos obligados a cancelar las promesas pendientes dentro del código probado usando el controlador de cancelación:
https://developers.google.com/web/updates/2017/09/abortable-fetch
Tener promesas de broma es igual a decir "La concurrencia es difícil, así que no la probemos". En realidad debería ser todo lo contrario.
Dado que la concurrencia es difícil, deberíamos probarla aún más y no permitir que una prueba pase con promesas pendientes.

Dado el lío de abortar promesas en esta pregunta de Stackoverflow, está claro que no es (AÚN) una cosa fácil de hacer:
https://stackoverflow.com/a/53933849/373542
Voy a intentar escribir una implementación de KISS para abortar mis promesas de recuperación y publicaré el resultado aquí.

@ giorgio-zamparelli: _ "La simultaneidad es difícil, así que no la probemos" _ está completamente fuera del punto del informe original. El problema no tiene que ver con las promesas pendientes, sino con el hecho de que esperar la propagación de la resolución de la promesa a través del código asíncrono en las pruebas es innecesariamente difícil.

Creo que las promesas de enrojecimiento curarían los síntomas en lugar de la enfermedad.

Las promesas deben resolverse normalmente en las pruebas sin necesidad de enrojecerlas.
Si hay una promesa pendiente en su prueba, debe esperar a que se resuelva utilizando, por ejemplo, wait de @testing-library/react O si la promesa pendiente no es parte del alcance de la prueba, debe hacerlo. burlarse del código iniciándolo o debe abortar la promesa pendiente en algún lugar como en el evento del ciclo de vida React willUnmount usando el AbortController

AbortController es una nueva API que casi nadie está usando y tengo la sensación de que es la solución para la mayoría de las promesas pendientes en las pruebas.

PRUEBA QUE ESTOY EQUIVOCADO:
Fácilmente se podría demostrar que estoy equivocado si alguien que informó haber tenido problemas con problemas pendientes en este número ya intentó usar AbortController y jest.mock y no fue suficiente.

@ giorgio-zamparelli: Tal vez el malentendido se deba a mi uso de la frase _ "eliminar todos los manejadores de promesas pendientes" _ (y si lo hace, lo siento). Como con suerte verá si lee la descripción del problema detenidamente, me refiero a "administradores pendientes de promesas".

Entonces, para reiterar, no estamos hablando de _pegar_ promesas aquí (de ninguna manera), sino más bien de eliminar la resolución de la promesa con la mínima molestia. O, en otras palabras, acerca de llegar de manera transparente y determinista desde el punto en que se resuelve una promesa hasta el punto en que se invocan todos los efectos posteriores vinculados a ella (para que podamos probar el resultado de esto).

Recientemente publiqué flush-microtasks para este propósito. Se toma prestado su implementación a partir Reaccionar, que es sorprendentemente más complejo que @jwbay 's solución aquí o @thymikee' s solución aquí . No estoy seguro de si la complejidad hace alguna diferencia significativa, pero supongo que tiene en cuenta los casos extremos no considerados por las otras soluciones en este hilo. Solo usé esa implementación porque react-testing-library usa (ver aquí ), pero no la expone.

import { flushMicroTasks } from 'flush-microtasks'

await flushMicroTasks()

@aleclarson ¿Hay alguna diferencia entre flush-microtasks y flush-promises?

@ramusus Parece que flush-promises usa el mismo enfoque que la solución de @jwbay .

https://github.com/kentor/flush-promises/blob/46f58770b14fb74ce1ff27da00837c7e722b9d06/index.js

RTL también ha copiado el código de React: https://github.com/testing-library/react-testing-library/blob/8db62fee6303d16e0d5c933ec1fab5841dd2109b/src/flush-microtasks.js

EDITAR: ja, ya mencionado: sonriendo:

No estoy seguro de que necesitemos integrarlo en Jest cuando la gente pueda usarlo. ¿Quizás podamos vincularlo en los documentos? Este problema se trata de eliminarlos sincrónicamente, lo que creo que va más allá de lo que queremos hacer (especialmente porque es imposible con async-await )

La solución flushPromises solo funciona en las promesas que se resuelven de inmediato, pero no en las que aún están pendientes.

Hmm, buen punto. No sé si es posible rastrear pending promesas de alguna manera. Podría ser capaz de hacer algo inteligente con async_hooks , no estoy seguro. Probablemente será doloroso intentar diferenciar entre las promesas creadas por el código de usuario y las promesas creadas por Jest y sus dependencias.

Intenté envolver / simular el objeto Promise para incluir un contador, pero eso no funciona:

const _promise = window.Promise;
window.Promise = function(promiseFunction){
    // counter
    return new _promise(promiseFunction);
}

El problema principal son las funciones async que no usan el Promise global en absoluto

Ok, encontré una forma realmente hacker como esta .

  1. Crea un nuevo módulo con una lista.
  2. Agregue sus promesas a esa lista.
  3. Resuelva las promesas en su prueba y elimínelas de la lista.
  4. En mi caso, ejecuto wrapper.update() de la enzima. Haga aquí algo similar si es necesario.
  5. Repita los pasos 3 y 4 hasta que la lista esté vacía.

Lo sé, no es una buena práctica ajustar el código a las pruebas, PERO ya uso esta lógica en la representación del lado del servidor. Pero al final solo está esperando. ¯ \ _ (ツ) _ / ¯

Hay una actualización interesante para esto en Jest 26, donde los temporizadores falsos ahora se basan en @ sinon / fake-timers (si están habilitados con jest.useFakeTimers('modern') ).

Probé los temporizadores falsos modernos con mis pruebas, y desafortunadamente hace que el truco await new Promise(resolve => setImmediate(resolve)); cuelgue indefinidamente. Afortunadamente, @sinon/fake-timers incluye varios métodos *Async() que "también romperán el ciclo de eventos, permitiendo que cualquier devolución de llamada de promesa programada se ejecute _antes_ de ejecutar los temporizadores". Desafortunadamente, no veo ninguna forma de obtener el objeto clock través de las API de Jest.

¿Alguien sabe cómo hacer que Jest nos dé ese objeto clock ?

Como otros, mi motivación para usar await new Promise(setImmediate); es eliminar las promesas que se pueden resolver, de modo que pueda probar unitariamente su impacto en el sistema.

Parecería que los temporizadores falsos "modernos" de hecho tienen un desempeño inferior al de otros, ya que caducan aparentemente sin sentido.

Aquí hay algunas pruebas unitarias para describir esto:

describe('flushing of js-queues using different timers', () => {
  beforeAll(() => {
    // It would take the failing test 5 long seconds to time out.
    jest.setTimeout(100);
  });

  it.each([
    [
      'given real timers',
      () => {
        jest.useRealTimers();
      },
    ],
    ['given no timers', () => {}],
    [
      'given "legacy" fake timers',
      () => {
        jest.useFakeTimers('legacy');
      },
    ],
    [
      // This is the the failing scenario, not working like the other timers.
      'given "modern" fake timers',
      () => {
        jest.useFakeTimers('modern');
      },
    ],
  ])(
    '%s, when using setImmediate to flush, flushes a promise without timing out',
    async (_, initializeScenarioSpecificTimers) => {
      initializeScenarioSpecificTimers();

      let promiseIsFlushed = false;

      Promise.resolve().then(() => {
        promiseIsFlushed = true;
      });

      // Flush promises
      await new Promise(setImmediate);

      expect(promiseIsFlushed).toBe(true);
    },
  );
});

Siento que la prueba anterior no debería fallar como lo hace.

Para mí, la solución fue eliminar las promesas utilizando el "setImmediate" nativo del nodo del paquete "temporizadores", en lugar del "setImmediate" global. Teniendo esto, pasa lo siguiente:

import { setImmediate as flushMicroTasks } from 'timers';

it('given "modern" fake timers, when using native timers to flush, flushes a promise without timing out', async () => {
  jest.useFakeTimers('modern');

  let promiseIsFlushed = false;

  Promise.resolve().then(() => {
    promiseIsFlushed = true;
  });

  // Flush micro and macro -tasks
  await new Promise(flushMicroTasks);

  expect(promiseIsFlushed).toBe(true);
});

Gracias @aleclarson.

Aquí está nuestra solución para este problema:

https://github.com/team-igniter-from-houston-inc/async-fn
https://medium.com/houston-io/how-to-unit-test-asynchronous-code-for-javascript-in-2020-41c124be2552

El código de prueba se puede escribir como:

// Note: asyncFn(), extends jest.fn() with a way to control resolving/rejecting of a promise
const load = asyncFn();

const afterLoad = jest.fn();
const result = 'mock result';

mount(<Component load={load} afterLoad={afterLoad} />);

// ... some interaction that requires the `load`

// Note: New way to controlling when promise resolves
await load.resolve(result);

expect(afterLoad).toHaveBeenCalledWith(result);

Tenga en cuenta que no necesita saber nada sobre promesas de descarga o temporizadores en ejecución.

@jansav agradable / + 1. Fwiw he visto ese enfoque llamado patrón diferido. Creo que hace mejores pruebas.

Me parece que el problema con los temporizadores falsos es que rompe el ciclo de ejecución natural de cómo se supone que funcionan los temporizadores. Me pregunto por qué no podemos simplemente hacer que las funciones de ejecución del temporizador de broma sean asincrónicas. Cambiar los temporizadores para que se resuelvan sincrónicamente hace que el código de prueba se vea ordenado, pero está causando este efecto secundario masivo.

mi caso de uso:

public static resolvingPromise<T>(result: T, delay: number = 5): Promise<T> {
    return new Promise((resolve) => {
        setTimeout(
            () => {
                resolve(result);
            },
            delay
        );
    });
}

archivo de prueba:

it("accepts delay as second parameter", async () => {
    const spy = jest.fn();
    MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
    jest.advanceTimersByTime(49);
    expect(spy).not.toHaveBeenCalled();
    jest.advanceTimersByTime(1);
    await Promise.resolve(); // without this line, this test won't pass
    expect(spy).toHaveBeenCalled();
});
¿Fue útil esta página
0 / 5 - 0 calificaciones