web-dev-qa-db-fra.com

Comment faire en sorte que Jest attende la fin de l'exécution de tout le code asynchrone avant d'attendre une assertion

Je suis en train d’écrire un test d’intégration pour une application React, c’est-à-dire un test qui teste plusieurs composants ensemble, et je veux me moquer de tout appel aux services externes.

Le problème est que le test semble s'exécuter avant le rappel asynchrone, ce qui provoque l'échec de mes tests.

Y a-t-il un moyen de contourner cela? Puis-je attendre que le code asynchrone d'appel se termine?

Voici quelques mauvais pseudo codes pour illustrer mon propos.

Je voudrais tester que lorsque je monte Parent, son composant enfant rend le contenu renvoyé par un service externe, ce que je vais me moquer.

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)
          }
    }

}

Lorsque je le fais dans mon test, il échoue car il s'exécute avant que le rappel ne soit déclenché.

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')
    });
});

Comment puis-je tester cela? Je comprends que angular résout ce problème avec zonejs. Existe-t-il une approche équivalente dans React?

18
Dan

Voici un extrait qui attend que les promesses en attente soient résolues:

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

Notez que setImmediate est une fonctionnalité non standard (et ne devrait pas devenir standard). Mais si cela suffit pour votre environnement de test, cela devrait être une bonne solution. Sa description:

Cette méthode est utilisée pour interrompre les opérations de longue durée et exécuter une fonction de rappel immédiatement après que le navigateur a terminé d'autres opérations telles que les événements et les mises à jour d'affichage.

Voici à peu près comment l'utiliser avec async/wait:

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 
});

Je l'ai beaucoup utilisé dans ce projet si vous voulez des exemples concrets de travail.

6
Tadas Antanavicius

Je ne suis au courant d'aucun élément natif de React pour accomplir ce que vous recherchez.

Cependant, j'ai pu accomplir cela dans un code similaire en appelant le @done de beforeAll () une fois l'installation terminée. Voir les modifications apportées à votre code ci-dessous:

let setupComplete;
jest.mock('eternalService', () => {
    return jest.fn(() => {
        return { doSomething: jest.fn((cb) => { cb('fakeReturnValue'); setupComplete(); }) };
});
.
.
.
    beforeAll(done => {
        parent = mount(<Parent />)
        setupComplete = done;
    });
});

Je ne les ai jamais utilisées, mais Jest's runAllTicks et runAllImmediates présente un intérêt potentiel.

0

Je vous suggère d'exporter aThingThatReturnsAPromise() à partir de son module ou de son fichier, puis de l'importer dans votre scénario de test.

Puisque aThingThatReturnsAPromise() renvoie une promesse, vous pouvez utiliser les fonctionnalités de test asynchrone de Jest. Jest attendra que votre promesse soit résolue et vous pourrez ensuite faire vos affirmations.

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');
        });
    });
});

Pour plus d'informations, lisez comment Jest gère les cas de test avec Promises dans les documents Jest ici

0
Saurabh Misra

bien que le pseudo-code puisse être modifié pour suivre le cycle de vie de React (avec componentWillMount()componentDidMount(), ce serait beaucoup plus facile à tester. Cependant, voici mon pseudo-code non testé pour les modifications de vos codes de test, n'hésitez pas à le tester et à le mettre à jour. J'espère que ça vous aidera!

describe('When rendering Parent', () => {
    it('should display Child with the response of the service', function(done) => {
        const parent = mount(<Parent />);
        expect(parent.html()).toMatch('fakeReturnValue');
        done();
    });
});
0
Jee Mok

La nature asynchrone de JavaScript est l'une des principales raisons pour lesquelles les tests unitaires de JavaScript ont pris si longtemps à mûrir. Jest fournit de nombreux modèles pour tester le code asynchrone décrit ici: http://jestjs.io/docs/fr/asynchronous.html I utilise async/wait le plus largement possible car il permet des tests extrêmement lisibles. 

Mais puisque vous vous moquez de la méthode asynchrone, aucun code asynchrone n'est nécessaire. Voir ci-dessous.

let parent
let eternalService = jest.fn(() => 'fakeReturnValue');

beforeEach(() => {
    parent = mount(<Parent />)
    parent.instance().externalService = externalService
})

describe('When rendering Parent', () => {
    it('should display Child with response of the service', () => {
        expect(parent.html()).toMatch('fakeReturnValue')
    });
});

0
landro