Как заставить Jest ждать завершения выполнения всего асинхронного кода, прежде чем ожидать утверждения

Я пишу интеграционный тест для приложения React, то есть тест, который тестирует множество компонентов вместе, и я хочу имитировать любые вызовы внешних сервисов.

Проблема в том, что кажется, что тест выполняется до того, как выполняется асинхронный обратный вызов, что приводит к сбою моих тестов.

Есть ли что-нибудь вокруг этого? Могу ли я как-то дождаться завершения асинхронного кода вызова?

Вот какой-то плохой псевдокод, чтобы проиллюстрировать мою точку зрения.

Я хотел бы проверить, что когда я монтирую Parent, его дочерний компонент отображает контент, который вернулся из внешней службы, над которым я буду издеваться.

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

}

Когда я делаю это в своем тесте, он терпит неудачу, потому что он выполняется до того, как запускается обратный вызов.

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

Как мне это проверить? Я понимаю, что angular решает это с помощью zonejs, есть ли эквивалентный подход в React?


person Dan    schedule 24.06.2017    source источник
comment
@Gegenwind Не имеет отношения к jestjs.io/docs/en/asynchronous.html? Это просто expect.assertions() вещь. Я не буду сбрасывать в ответ пример из документа ...   -  person Adriano Repetti    schedule 27.06.2018


Ответы (5)


Вот фрагмент, который ждет, пока ожидающие обещания не будут разрешены:

const flushPromises = () => new Promise(setImmediate);

Обратите внимание, что setImmediate - нестандартная функция (и не ожидается, что он станет стандартом). Но если этого достаточно для вашей тестовой среды, это должно быть хорошим решением. Его описание:

Этот метод используется для прерывания длительных операций и запуска функции обратного вызова сразу после того, как браузер завершил другие операции, такие как события и обновления отображения.

Вот примерно как использовать его с помощью 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 
});

Я много использовал это в этом проекте, если вы хотите, чтобы кто-то работал реально. мировые примеры.

person Tadas Antanavicius    schedule 26.06.2018
comment
Работает отлично! Если вы хотите сделать это немного более кратким, вы можете еще больше упростить эту функцию: const flushPromises = () => new Promise(setImmediate); - person Arbiter; 28.04.2019
comment
в чем разница между этим flushPromises и await Promise.resolve()? Как по-другому они ждут очереди микрозадач? Спрашиваю, потому что я могу использовать Promise.resolve, когда нужно выполнить только одно обещание, но в противном случае flushPromises необходимо. - person Nikola Dim; 29.04.2020
comment
Эта функция действительно помогла, но вскоре я понял, почему она не ждала. Строка, которая обрабатывала обещание, не имела await в начале. В моем случае это не повлияло на корректность программы, но, возможно, это не лучшее решение для всех. - person TastyWheat; 06.08.2020
comment
Ух ты! Искал это решение два дня! Большое спасибо! - person Ruslan Valeev; 12.04.2021
comment
На самом деле это не соответствует заданному вопросу. Это (или даже просто await Promise.resolve()) фактически поместит оставшуюся часть теста в заднюю часть стека за обещаниями, которые уже выполнены, но он не будет ждать каких-либо обещаний, для выполнения которых все еще требуется время. - person erich2k8; 04.05.2021
comment
Стоит отметить, что setImmediate был удален из jsdom в jest 27 (github.com/facebook/jest/pull/ 11222), поэтому в шутливых тестах это больше не работает. - person Kam Figy; 25.06.2021

Я предлагаю вам экспортировать aThingThatReturnsAPromise() из его модуля или файла, а затем импортировать его в свой тестовый пример.

Поскольку aThingThatReturnsAPromise() возвращает обещание, вы можете использовать функции асинхронного тестирования Jest. Jest будет ждать выполнения вашего обещания, а затем вы сможете делать свои утверждения.

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

Дополнительные сведения см. В здесь Jest Docs, как Jest обрабатывает тестовые случаи с помощью обещаний.

person Saurabh Misra    schedule 27.06.2018

Я ничего не знаю о React, чтобы выполнить то, что вы ищете.

Однако мне удалось сделать это в аналогичном коде, вызвав доAll () @done после завершения установки. Смотрите изменения в вашем коде ниже:

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

Я никогда ими не пользовался, но потенциальный интерес представляет runAllTicks и runAllImmediates.

person Zachary Ryan Smith    schedule 07.10.2017

в то время как псевдокод можно реорганизовать, чтобы он соответствовал жизненному циклу React (используя componentWillMount() componentDidMount(), было бы намного проще протестировать. Однако ниже мой непроверенный псевдокод для изменений для ваших тестовых кодов, не стесняйтесь тестировать и обновлять его. Надеюсь, это вам поможет!

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();
    });
});
person Jee Mok    schedule 21.06.2018

В качестве альтернативы некоторым методам, перечисленным в других ответах, вы также можете использовать модуль npm flush -обещания. Ниже показан образец набора тестов с двумя тестами (также как и по указанному URL-адресу):

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

    });

});
person Alan C. S.    schedule 23.01.2021
comment
Обратите внимание, что модуль flush-promises просто использует технику const flushPromises = () => new Promise(setImmediate) из принятого ответа (с откатом на setTimeout, если setImmediate не не существует). - person Josh Kelley; 27.04.2021