如何使Jest等待所有异步代码完成执行后才期待Assert

eqqqjvef  于 2023-02-27  发布在  Jest
关注(0)|答案(7)|浏览(268)

我正在为React应用程序编写集成测试,即一起测试多个组件的测试,并且我希望模拟对外部服务的任何调用。
问题是测试似乎在异步回调执行之前执行,导致我的测试失败。
这周围有什么办法吗?我可以等待调用异步代码完成吗?
下面是一些糟糕的代码来说明我的观点。
我想测试一下,当我挂载Parent时,它的Child组件呈现从外部服务返回的内容,我将模拟它。

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中是否有等效的方法?

lx0bsm1f

lx0bsm1f1#

针对Jest 27+更新

对于jest 27+,您还可以使用process.nextTick:

await new Promise(process.nextTick);

(感谢评论中的Adrian Godong)

原始答案

下面是一个等待未决承诺得到解决的代码段:

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

如果您需要一些实际的示例,我经常使用in this project

pkbketx9

pkbketx92#

flushPromises方法在某些情况下是失败。
只需使用await Promise.resolve()即可:

const component = mount(<App/>);

component.find('<button>').simulate('click');

// State changes

await Promise.resolve();

// Assert changes that occurred on the component
50few1ms

50few1ms3#

我建议您将aThingThatReturnsAPromise()从其模块或文件中导出,然后将其导入到测试用例中。
由于aThingThatReturnsAPromise()返回一个承诺,因此可以利用Jest的异步测试特性,Jest将等待您的承诺得到解决,然后您可以进行Assert。

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文档中Jest如何处理带有Promises的测试用例

v8wbuo2f

v8wbuo2f4#

作为其他答案中列出的一些技术的替代方法,您也可以使用npm模块flush-promises。下面显示了一个包含两个测试的示例测试套件(参考的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);

    });

});
xtupzzrd

xtupzzrd5#

我不知道任何原生的React,以完成你正在寻找的。
不过,我可以在安装完成后调用beforeAll()的@done,用类似的代码来完成这一点。

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

我从来没有使用过它们,但可能感兴趣的是Jest的runAllTicks和runAllImmediates。

w8rqjzmb

w8rqjzmb6#

虽然伪代码可以被重构以遵循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();
    });
});
uidvcgyl

uidvcgyl7#

其他的答案基本上可以归结为:“创造一个新的承诺并等待它解决”是不好的。他们只是等待当前悬而未决的承诺解决,但如果解决当前悬而未决的承诺创造了新的悬而未决的承诺!?你只能涵盖一轮的承诺使用类似

await Promise.resolve();

一个更通用的解决方案是用测试库中的await act Package 您的render函数,如下所示:

let component;
await act(()=>{
    component = render(<Whatever />);
});

在此之后,在任何useEffect链中创建的所有承诺,甚至改变状态(响应于fetch等等)的useEffect,其导致更多的useEffect运行,从而执行更多的fetch等等--所有承诺将被解析并准备好Assert,而无需像测试库的waitForfindBy*等等的异步机制。

相关问题