Estoy escribiendo una prueba de integración para una aplicación React, es decir, una prueba que prueba muchos componentes juntos, y quiero simular cualquier llamada a servicios externos.
El problema es que la prueba parece ejecutarse antes de que se ejecute la devolución de llamada asíncrona, lo que hace que mis pruebas fallen.
¿Hay alguna forma alrededor de esto? ¿Puedo de alguna manera esperar a que termine el código asincrónico de llamada?
Aquí hay un pseudo código incorrecto para ilustrar mi punto.
Me gustaría probar que cuando monto Parent, su componente Child representa el contenido que regresó de un servicio externo, del cual me burlaré.
class Parent extends component { render () { <div> <Child /> </div> } } class Child extends component { DoStuff() { aThingThatReturnsAPromise().then((result) => { Store.Result = result }) } render() { DoStuff() return(<div>{Store.Result}</div>) } } function aThingThatReturnsAPromise() { return new Promise(resolve =>{ eternalService.doSomething(function callback(result) { resolve(result) } } }Cuando hago esto en mi prueba, falla porque se ejecuta antes de que se active la devolución de llamada.
jest.mock('eternalService', () => { return jest.fn(() => { return { doSomething: jest.fn((cb) => cb('fakeReturnValue'); }); }); describe('When rendering Parent', () => { var parent; beforeAll(() => { parent = mount(<Parent />) }); it('should display Child with response of the service', () => { expect(parent.html()).toMatch('fakeReturnValue') }); });¿Cómo pruebo esto? Entiendo que angular resuelve esto con zonejs, ¿hay un enfoque equivalente en React?
Actualizado para Jest 27+
Para jest 27+, también puedes usar process.nextTick:
await new Promise(process.nextTick);(Gracias a Adrian Godong en los comentarios)
Respuesta Original
Aquí hay un fragmento que espera hasta que se resuelvan las Promesas pendientes:
const flushPromises = () => new Promise(setImmediate);Tenga en cuenta que setImmediate es una característica no estándar (y no se espera que se convierta en estándar). Pero si es suficiente para su entorno de prueba, debería ser una buena solución. Su descripción:
Este método se utiliza para dividir operaciones de ejecución prolongada y ejecutar una función de devolución de llamada inmediatamente después de que el navegador haya completado otras operaciones, como eventos y actualizaciones de pantalla.
Aquí se explica aproximadamente cómo usarlo usando async/await:
it('is an example using flushPromises', async () => { const wrapper = mount(<App/>); await flushPromises(); wrapper.update(); // In my experience, Enzyme didn't always facilitate component updates based on state changes resulting from Promises -- hence this forced re-render // make assertions });Usé esto mucho en este proyecto si quieres algunos ejemplos que funcionen en el mundo real.
Le sugiero que exporte aThingThatReturnsAPromise() desde su módulo o archivo y luego lo importe a su caso de prueba.
Dado que aThingThatReturnsAPromise() devuelve una promesa, puede utilizar las características de prueba asíncrona de Jest. Jest esperará a que tu promesa se resuelva y luego podrás hacer tus afirmaciones.
describe('When rendering Parent', () => { var parent; beforeAll(() => { parent = mount(<Parent />) }); it('should display Child with response of the service', () => { expect.assertions(1); return aThingThatReturnsAPromise().then( () => { expect(parent.html()).toMatch('fakeReturnValue'); }); }); });Para obtener más información, lea cómo Jest maneja los casos de prueba con Promesas en Jest Docs aquí
Como alternativa a algunas de las técnicas enumeradas en otras respuestas, también puede usar el módulo npm flush-promises . A continuación se muestra un conjunto de pruebas de muestra con dos pruebas (como también se muestra en la URL a la que se hace referencia):
const flushPromises = require('flush-promises'); describe('Async Promise Test Suite', () => { it('A test involving flushPromises', async () => { const wrapper = mount(<App/>); await flushPromises(); // more code }); it('Will not work correctly without flushing promises', async () => { let a; let b; Promise.resolve().then(() => { a = 1; }).then(() => { b = 2; }) await flushPromises(); expect(a).toBe(1); expect(b).toBe(2); }); });