如何在 React 应用程序中使用 JEST 测试向 api 发出 axios 请求的异步函数

Posted

技术标签:

【中文标题】如何在 React 应用程序中使用 JEST 测试向 api 发出 axios 请求的异步函数【英文标题】:How to test an async function which does an axios request to an api using JEST in a React application 【发布时间】: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"  />
        </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)(在测试的最后)吗? 您提供的解决方案似乎有效。通过测试。谢谢。 【参考方案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" />
        </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 是否返回模拟异步结果

以上是关于如何在 React 应用程序中使用 JEST 测试向 api 发出 axios 请求的异步函数的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jest 在 React Native 中测试警报

如何使用 create-react-app 在 Jest 测试中忽略生成的 Relay 查询文件?

如何在使用 react-testing-library 和 jest 完成的单元测试中启用 react-i18n 翻译文件?

如何使用 Jest 和/或 Enzyme 获取嵌套在 React 组件中的元素的属性?

如何模拟在使用 Jest 测试的 React 组件中进行的 API 调用

如何在 React-Native 项目中使用 Jest 测试样式化组件?