【问题标题】:How to test an async function which does an axios request to an api using JEST in a React application如何在 React 应用程序中使用 JEST 测试向 api 发出 axios 请求的异步函数
【发布时间】:2019-05-29 07:35:52
【问题描述】:

我正在尝试测试一个 async/await 函数,该函数使用 axios 进行 api 调用以获取用户。我是测试 React 应用程序和使用 JEST(第一次尝试)的新手,无法运行测试。

我尝试在 JEST 中使用模拟函数。我的代码如下:

  // component --users

  export default class Users extends React.Component {
      constructor(props) {
        super(props);
        this.state = {};
        this.getUsers = this.getUsers.bind(this);
       }

  /*does an api request to get the users*/
  /*async await used to handle the asynchronous behaviour*/
  async getUsers() {
    // Promise is resolved and value is inside of the resp const.
     try {
      const resp = await axios.get(
        "https://jsonplaceholder.typicode.com/users"
      );

      if (resp.status === 200) {
        const users = resp.data;
        /* mapped user data to just get id and username*/
        const userdata = users.map(user => {
          var userObj = {};
          userObj["id"] = user.id;
          userObj["username"] = user.username;
          return userObj;
        });
        return userdata;
      }
    } catch (err) {
      console.error(err);
    }
  }


  componentDidMount() {
    this.getUsers().then(users => this.setState({ users: users }));
  }

  /*****************************************************************/
  //props(userid ,username) are passed so that api call is made for
  //getting posts of s psrticular user .
  /*****************************************************************/
  render() {
    if (!this.state.users) {
      return (
        <div className="usersWrapper">
          <img className="loading" src="/loading.gif" alt="Loading.." />
        </div>
      );
    }

    return (
      <div className="usersWrapper">

        {this.state.users.map(user => (
          <div key={user.id}>
            <Posts username={user.username} userid={user.id} />
          </div>
        ))}
      </div>
    );
  }
}


//axios.js-mockaxios
export default {
    get: jest.fn(() => Promise.resolve({ data: {} }))
};

//users.test.js


describe("Users", () => {
  describe("componentDidMount", () => {
    it("sets the state componentDidMount", async () => {
      mockAxios.get.mockImplementationOnce(
        () =>
          Promise.resolve({
            users: [
              {
                id: 1,
                username: "Bret"
              }
            ]
          }) //promise
      );

      const renderedComponent = await shallow(<Users />);
      await renderedComponent.update();
      expect(renderedComponent.find("users").length).toEqual(1);
    });
  });
});

测试失败 -

失败 src/components/users.test.js (7.437s) ● 用户 › componentDidMount › 设置状态 componentDidMount

expect(received).toEqual(expected)

期望值等于: 1 已收到: 0

请帮我找出问题所在。我对测试 reactapps 完全陌生

【问题讨论】:

  • 您问题中的代码没有显示getUsers 的调用方式,也没有显示您的测试断言。有没有您遗漏的componentDidMount 方法?还是您的测试直接致电getUsers?如果删除 try/catch 会发生什么?
  • getUsers() 在 componentDidMount() 中被调用。它在发布的代码中。
  • 非常抱歉;我以某种方式错过了滚动条。
  • shallow 不是异步方法,在它之前不需要await。看起来测试提前完成,然后组件在获得用户后重新渲染,您可以检查setTimeout( () =&gt; expect(renderedComponent.find("users").length).toEqual(1), 0)(在测试的最后)吗?
  • 您提供的解决方案似乎有效。通过测试。谢谢。

标签: reactjs async-await jestjs enzyme


【解决方案1】:

据我了解,您正在尝试做的是等待承诺的解决,该承诺“隐藏”在您的 componentDidMount() 方法中。

expect(renderedComponent.find("users").length).toEqual(1);

在我看来在这种情况下将不起作用,因为find() 正在尝试选择 DOM 元素。如果要查看状态,需要使用state()

expect(renderedComponent.state("users").length).toEqual(1);

不过,你必须找到正确的方法来等待承诺的解决:

要参考以前的帖子和 cmets,我看不出将 async/await 与任何酶方法(更新、实例或其他任何方法)结合使用有任何效果。酶文档也没有给出这个方向的提示,因为承诺解决不是酶的工作)。

唯一稳健、干净且 (jest-default) 的前进方式是以某种方式混合不同的方法。您的代码略有改动:

// component --users

export default class Users extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.getUsers = this.getUsers.bind(this);
  }

  /*does an api request to get the users*/

  /*async await used to handle the asynchronous behaviour*/
  async getUsers() {
    // Promise is resolved and value is inside of the resp const.
    try {
      const resp = await axios.get(
        "https://jsonplaceholder.typicode.com/users"
      );

      if (resp.status === 200) {
        const users = resp.data;
        /* mapped user data to just get id and username*/
        const userdata = users.map(user => {
          var userObj = {};
          userObj["id"] = user.id;
          userObj["username"] = user.username;
          return userObj;
        });
        return userdata;
      }
    } catch (err) {
      console.error(err);
    }
  }


  componentDidMount() {
    // store the promise in a new field, not the state since setState will trigger a rerender
    this.loadingPromise = this.getUsers().then(users => this.setState({users: users}));
  }

  /*****************************************************************/
  //props(userid ,username) are passed so that api call is made for
  //getting posts of s psrticular user .
  /*****************************************************************/
  render() {
    if (!this.state.users) {
      return (
        <div className="usersWrapper">
          <img className="loading" src="/loading.gif" alt="Loading.."/>
        </div>
      );
    }

    return (
      <div className="usersWrapper">

        {this.state.users.map(user => (
          <div key={user.id}>
            <Posts username={user.username} userid={user.id}/>
          </div>
        ))}
      </div>
    );
  }
}


//axios.js-mockaxios
export default {
  get: jest.fn(() => Promise.resolve({data: {}}))
};

//users.test.js
describe("Users", () => {
  describe("componentDidMount", () => {
    it("sets the state componentDidMount",() => {
      mockAxios.get.mockImplementationOnce(
        () =>
          Promise.resolve({
            users: [
              {
                id: 1,
                username: "Bret"
              }
            ]
          }) //promise
      );

      const renderedComponent = shallow(<Users/>);
      //Jest will wait for the returned promise to resolve.
      return renderedComponent.instance().loadingPromise.then(() => {
        expect(renderedComponent.state("users").length).toEqual(1);
      });
    });
  });
});

在做一些研究时,我遇到了这个post:老实说,我不知道这是否是一个好主意,但它在我的异步测试中有效,它避免了在你的零件。所以也可以随意尝试一下!

【讨论】:

    【解决方案2】:

    看起来像similar to this one

    问题是测试是否在异步 fetchUsers 和 setState 之前完成(它也是异步操作)。要修复它,您可以通过done 回调进行测试,并将最后一个期望放入setTimeout(fn, 0) - 所以在所有异步操作完成后将调用期望:

    it("sets the state componentDidMount", (done) => {
      ...
      setTimeout(() => {
        expect(renderedComponent.find("users").length).toEqual(1);
        done();
      }, 0);
    });
    

    正如评论中提到的,这是一个hacky修复,我希望这里有其他答案,有更多jest修复它的方法。

    【讨论】:

    • 你不应该通过添加超时来解决异步单元测试问题
    • @MartinVich,是的,伙计,我也觉得这有点老套。那你是怎么解决的?
    • 你需要告诉 jest 它必须等待 promise 解决。见jestjs.io/docs/en/tutorial-async#async-await
    • @MartinVich,好的,但是 1)在原始问题中没有办法捕捉已解决的承诺,因为承诺被封装到 componentDidMount 2)即使我们抓住了 1 中的时刻,怎么会你赶上setState 完成时刻了吗?它也是异步的......你能详细说明答案吗?我很好奇correctly 是怎么解决的
    • 没错。我认为componentDidMount 测试应该模拟getUsers 以返回一些值,然后测试该值是否传递给setState。然后在单独的测试中检查getUsers 是否返回模拟异步结果
    猜你喜欢
    • 2020-08-04
    • 1970-01-01
    • 2021-06-04
    • 2021-01-30
    • 2021-08-25
    • 2022-11-10
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多