Jest:测试不能在setImmediate或process.nextTick回调中失败

时间:2017-01-22 15:45:48

标签: node.js reactjs jasmine jestjs

我正在尝试为需要在其componentWillMount方法中完成异步操作的React组件编写测试。 componentWillMount调用一个函数,作为prop传递,它返回一个promise,我在我的测试中模拟了这个函数。

这样可以正常工作,但是如果测试在调用setImmediateprocess.nextTick时失败,则Jest不会处理该异常并且它会过早退出。下面,您可以看到我甚至尝试捕获此异常,但无济于事。

如何在Jest中使用setImmediatenextTick之类的内容?这个问题的公认答案是我试图实施失败的原因:React Enzyme - Test `componentDidMount` Async Call

it('should render with container class after getting payload', (done) => {
  let resolveGetPayload;
  let getPayload = function() {
    return new Promise(function (resolve, reject) {
      resolveGetPayload = resolve;
    });
  }
  const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);

  resolveGetPayload({
    fullname: 'Alex Paterson'
  });

  try {
    // setImmediate(() => {
    process.nextTick(() => {
      expect(enzymeWrapper.hasClass('container')).not.toBe(true); // Should and does fail
      done();
    });
  } catch (e) {
    console.log(e); // Never makes it here
    done(e);
  }
});

Jest v18.1.0

节点v6.9.1

6 个答案:

答案 0 :(得分:2)

有些事情需要注意;

  • process.nextTick 异步,因此try / catch无法捕获它。
  • 即使你在Promise中运行的代码是同步的,
  • Promise也会解析/拒绝异步。

试一试

it('should render with container class after getting payload', (done) => {
    const getPayload = Promise.resolve({
        fullname: 'Alex Paterson'
    });
    const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);

    process.nextTick(() => {
        try {
            expect(enzymeWrapper.hasClass('container')).not.toBe(true);
        } catch (e) {
            return done(e);
        }
        done();
    });
});

答案 1 :(得分:2)

以下一种方式克服该问题atm(也解决了Enzyme和componentDidMount和async setState中的异步调用问题):

it('should render proper number of messages based on itemsPerPortion', (done) => {
  const component = shallow(<PublishedMessages itemsPerPortion={2} messagesStore={mockMessagesStore()} />);

  setImmediate(() => { // <-- that solves async setState in componentDidMount
    component.update();

    try { // <-- that solves Jest crash
      expect(component.find('.item').length).toBe(2);
    } catch (e) {
      return fail(e);
    }

    done();
  });
});

(酶3.2.0,Jest 21.1.6)

<强>更新

使用async / await(并且它仍在解决async componentDidMount和async setState)时,想出了另一个更好(但仍然很奇怪)的解决方案:

it('should render proper number of messages based on itemsPerPortion', async () => {
  // Magic below is in "await", looks as that allows componentDidMount and async setState to complete
  const component = await shallow(<PublishedMessages itemsPerPortion={2} messagesStore={mockMessagesStore()} />);

  component.update(); // still needed
  expect(component.find('.item').length).toBe(2);
});

其他与异步相关的操作也应该以{{1​​}}为前缀,以及

await

答案 2 :(得分:1)

继弗拉基米尔的回答+编辑之后,这里有一个对我有用的替代方案。而不是await装载,await wrapper.update()

it('...', async () => {

  let initialValue;
  let mountedValue;

  const wrapper = shallow(<Component {...props} />);
  initialValue = wrapper.state().value;

  await wrapper.update(); // componentDidMount containing async function fires
  mountedValue = wrapper.state().value;

  expect(mountedValue).not.toBe(initialValue);
});

答案 3 :(得分:1)

另一个可能更清洁的解决方案,使用async / await并利用jest / mocha的功能来检测返回的诺言:

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

it('should render with container class after getting payload', async () => {
  let resolveGetPayload;
  let getPayload = function() {
    return new Promise(function (resolve, reject) {
      resolveGetPayload = resolve;
    });
  }
  const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);

  resolveGetPayload({
    fullname: 'Alex Paterson'
  });

  await currentEventLoopEnd(); // <-- clean and clear !

  expect(enzymeWrapper.hasClass('container')).not.toBe(true);
});

答案 4 :(得分:0)

- 发布作为答案的人无法在评论中格式化代码块。 -

在弗拉基米尔的回答基础上,请注意使用async / await也可以在beforeEach中使用:

var wrapper

beforeEach(async () => {
  // Let's say Foobar's componentDidMount triggers async API call(s)
  // resolved in a single Promise (use Promise.all for multiple calls).
  wrapper = await shallow(<Foobar />)
})

it('does something', () => {
  // no need to use an async test anymore!
  expect(wrapper.state().asynchronouslyLoadedData).toEqual(…)
})

答案 5 :(得分:0)

如其他人所示,将传递给process.nextTick / setImmediate的回调块包装为try / catch是可行的,但这很冗长且分散注意力。

一种更清洁的方法是使用await new Promise(setImmediate);测试回调内的简短行async来刷新承诺。这是一个使用此示例的示例,它使useEffect中的HTTP请求(对componentDidMount同样有用)在运行断言之前解析并触发重新渲染:

组件(LatestGist.js):

import axios from "axios";
import {useState, useEffect} from "react";

export default () => {
  const [gists, setGists] = useState([]);

  useEffect(() => getGists(), []);

  const getGists = async () => {
    const res = await axios.get("https://api.github.com/gists");
    setGists(res.data);
  };

  return (
    <>
      {gists.length
        ? <div data-test="test-latest-gist">
            the latest gist was made on {gists[0].created_at} by {gists[0].owner.login}
          </div>
        : <div>loading...</div>}
    </>
  );
};

测试(LatestGist.test.js):

import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import LatestGist from "./LatestGist";

jest.mock("axios");

describe("LatestGist", () => {
  beforeEach(() => jest.resetAllMocks());

  it("should load the latest gist", async () => {
    mockAxios.get.mockImplementationOnce(() => Promise.resolve({
      data: [{owner: {login: "test name"}, created_at: "some date"}],
      status: 200
    }));

    const wrapper = mount(<LatestGist />);
    let gist = wrapper.find('[data-test="test-latest-gist"]');
    expect(gist.exists()).toBe(false);

    await new Promise(setImmediate);
    wrapper.update();

    expect(mockAxios.get).toHaveBeenCalledTimes(1);
    gist = wrapper.find('[data-test="test-latest-gist"]');
    expect(gist.exists()).toBe(true);
    expect(gist.text()).toContain("test name");
    expect(gist.text()).toContain("some date");
  });
});

使用expect(gist.text()).toContain("foobar");这样的行强制失败的声明不会导致套件崩溃:

● LatestGist › should load the latest gist

expect(string).toContain(value)

  Expected string:
    "the latest gist was made on some date by test name"
  To contain value:
    "foobar"

    at Object.it (src/LatestGist.test.js:30:25)