如何在React组件中测试api调用并在api调用成功后期望视图更改?

时间:2018-12-16 08:16:40

标签: reactjs jestjs enzyme

我有一个只有一个按钮的简单react组件,单击该按钮后,它会使用fetch进行api调用,成功调用是调用setState来更新该组件。

在我的my-button.jsx文件中

import React from "react";

export default class MyButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            user: null
        }
        this.getUser = this.getUser.bind(this);
    }

    async getUser() {
        try {
            const res = await fetch("http://localhost:3000/users");
            if (res.status >= 400)
                throw new Error("something went wrong");

            const user = await res.json();
            this.setState({ user });

        } catch (err) {
            console.error(err);
        }


    }

    render() {

        return (
            <div>
                <button onClick={this.getUser}>Click Me</button>
                {this.state.user ? <p>got user</p> : null}
            </div>
        )
    }


}

在我的测试文件中

import React from "react";
import { shallow, Mount } from "enzyme";
import MyButton from "../my-button";


beforeAll(() => {
    global.fetch = jest.fn();
});

it("must test the button click", (done) => {

    fetch.mockImplementation(() => {
        return Promise.resolve({
            status: 200,
            json: () => {
                return Promise.resolve({ name: "Manas", userId: 2 });
            }
        });
    });
    const wrapper = shallow(<MyButton />);


    wrapper.find("button").simulate("click");

    //here using setTimeout to delay the find call, How to avoid using setTimeout
    setTimeout(() => {
        wrapper.update();
        expect(wrapper.find("p").length).toBe(1);
        fetch.mockClear();
        done();
    }, 1000)
})

我正在使用setTimeout来延迟期望调用,以避免如何避免使用setTimeout,因为这不是有效的测试方法。

如果我不使用setTimeout,则测试失败

 src/App.test.js
 FAIL  src/components/__test__/my-button.test.js
  ● must test the button click

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 0

      26 |     // setTimeout(() => {
      27 |     wrapper.update();
    > 28 |     expect(wrapper.find("p").length).toBe(1);
         |                                      ^
      29 |     fetch.mockClear();
      30 |     done();
      31 |     // }, 1000)

      at Object.toBe (src/components/__test__/my-button.test.js:28:38)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total

2 个答案:

答案 0 :(得分:2)

使用settimeout强制执行此顺序以达到测试期望。

getUsertestExpectations

对于当前的MyButton实现,没有直接的方法可以实现。需要提取getUser并将其作为对MyButton的支持,以便可以对getUser <的promise链 ie 链测试期望进行更好的控制/ p>

样品

getUser().then(testExpectations)

在重构的第一步,调用按钮getUser中的onClick来代替对组件ShallowWrapper的模拟调用。

这是Simulation的工作,但它返回一个包装实例。你不要这个您希望通过调用getUser来返回承诺,以便可以链接到它。

it("must test the button click", (done) => {
  fetch.mockImplementation(() => {
    return Promise.resolve({
      status: 200,
      json: () => Promise.resolve({ name: "Manas", userId: 2 })
    });
  });

  const wrapper = shallow(<MyButton />);
  const button = wrapper.find("button");
  const onClick = button.prop('onClick');

  onClick().then(() => {
    wrapper.update();
    expect(wrapper.find("p").length).toBe(1);
    fetch.mockClear();
    done();
  })
})

重构的下一步将是将getUser作为属性转发到MyButton。如果您发现MyButton始终将特定的实现用于其点击事件处理程序,则可能没有必要。

答案 1 :(得分:0)

如果在测试运行期间进行异步调用,则必须在事件循环结束时运行断言/期望。

it('must test the button click', done => {
  fetch.mockImplementation(() => {
    return Promise.resolve({
      status: 200,
      json: () => {
        return Promise.resolve({ name: 'Manas', userId: 2 });
      }
    });
  });
  const wrapper = shallow(<MyButton />);

  wrapper.find('button').simulate('click'); // async invocation

  // wait till async action is done
  new Promise((resolve, reject) => {
    setImmediate(() => {
      resolve();
    }, 0);
  }).then(() => {
    wrapper.update(); // you probably won't need this line
    expect(wrapper.find('p').length).toBe(1);
    fetch.mockClear();
    done();
  });
});

通常我在所有项目中都将其写为util方法。

// test-helper.js
export const waitForAsyncActionsToFinish = () => {
  return new Promise((resolve, reject) => {
    setImmediate(() => {
      resolve();
    }, 0);
  });
};

it('test something', (done) => {
  // mock some async actions
  const wrapper = shallow(<Component />);

  // componentDidMount async actions
  waitForAsyncActionsToFinish().then(() => {
    wrapper.find('.element').simulate('click');

    // onClick async actions - you have to wait again
    waitForAsyncActionsToFinish().then(() => {
      expect(wrapper.state.key).toEqual('val');
      done();
    });
  });
});