使用Jest和Redux的异步组件快照

时间:2017-03-13 21:11:01

标签: reactjs asynchronous redux jestjs

我发现Jest在测试我的Redux React应用程序时非常有用。但是,有很多关于如何测试异步操作创建器的示例,但我无法弄清楚如何对异步组件进行快照。

我想做的是与hovered link example from Facebook's own tutorial类似的事情。他们在onMouseEnter()上调用props函数,然后拍摄快照。如果onMouseEnter()调度使用Redux Thunk创建的异步操作,是否有一种简单的方法可以做到这一点?

这就是我的thunk的样子,它使用了axios。

  // test-api.js

  export function getLinkInfo() {
  return function(dispatch) {
    return axios.get('/api/get-link-info')
    .then(response => {
        dispatch(getLinkInfoSuccess(response.data));
        return response;
    });
  };
}

这是我自己的链接组件。

import React from 'react';
import { connect } from 'react-redux';
import * as api from '../../api/test-api';

class Link extends React.Component {
  render() {
    return (
      <a href='#' onMouseEnter={this.props.getLinkInfo}>
        Hover me
      </a>
      <div>{this.props.linkInfo}</div>
    );
  }
}

const mapDispatchToProps = function(dispatch) {
  return {
    getLinkInfo: function() {
      dispatch(api.getLinkInfo());
    }
  }
}

const mapStateToProps = function(store) {
  return {
    linkInfo: store.getIn(['testState', 'linkInfo'], "")
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Link);

最后是测试文件。

import React from 'react';
import Link from '../link';
import renderer from 'react-test-renderer';

test('Link changes linkInfo when hovered', () => {
  const component = renderer.create(
    <Link></Link>
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

2 个答案:

答案 0 :(得分:1)

问题在于,当你想测试异步内容时,你需要在测试中使用promise的实例,要么从测试中返回它,所以jest知道它并且可以等待它,或者在测试中使用async await它自我(docs)

你可以做的是模拟测试中的api:

从&#39;路径/导入{getLinkInfo}到/ / / api&#39; jest.mock(&#39; path / to / the / api&#39;,()=&gt;({   getLinkInfo:jest.fn() }))

这将使用具有getLinkInfo间谍的对象覆盖模块。然后导入模块,以便在测试中设置间谍的实际实现。

test('Link changes linkInfo when hovered', () = > {
  //create a new promise that can be returned from your test
  const p = new Promise((resolve) = > {
    //set the spy to make the request and resolve the promise
    getInfo.mockImplementation(function (dispatch) {
      return axios.get('/api/get-link-info')
        .then(response = > {
          dispatch(getLinkInfoSuccess(response.data));
          resolve(response);
        });
    };)
  };)
  const component = renderer.create(
    <Link></Link>
  );
  let tree = component.toJSON();
  expect(tree)
    .toMatchSnapshot();
  // manually trigger the callback
  tree.props.onMouseEnter();
  return p.then(() = > {
    tree = component.toJSON();
    expect(tree)
      .toMatchSnapshot()
  })
});

虽然这可以解决您的实际问题,但我建议不要像这样运行您的测试,调用您的真实API,还要自行模拟请求。首先,您的测试速度会快得多,并且不依赖于某些正在运行的后端。

关键是你要将React组件作为一个单元进行测试,因此它不关心调用getLinkInfo后发生的事情。这些是getLinkInfo的单元测试的详细信息。你的组件知道的是,它调用getLinkInfo传递一个回调,有时会调用这个回调。当它被调用时,其间发生的事情不是组件责任的一部分。如果您考虑这样的测试,最简单的解决方案是立即调用回调。

test('Link changes linkInfo when hovered', () = > {
  getInfo.mockImplementation(function (dispatch) {
    dispatch({
      some: 'Data'
    });
  };)
  const component = renderer.create(
    <Link></Link>
  );
  let tree = component.toJSON();
  expect(tree)
    .toMatchSnapshot();
  // manually trigger the callback
  tree.props.onMouseEnter();
  tree = component.toJSON();
  expect(tree).toMatchSnapshot()
});

答案 1 :(得分:0)

在教程中,他们有一个有状态的组件。这需要进行这样的“体操”。

对于一个纯粹的无状态组件,你应该只测试两件事:

  1. 使用props
  2. 的任意组合正确呈现
  3. 在某个事件上调用正确的处理程序。
  4. 但是,您只导出connect生成的HOC。您可以通过导出两者(以及mapDispatchToPropsmapStateToProps)来解决此问题。或者,通过模拟connect使其返回测试的原始组件。

    该文件如下所示:

    import …
    
    export class Link extends React.Component {
        …
    }
    
    export const mapDispatchToProps = …
    
    export const mapStateToProps = …
    
    export default connect(mapStateToProps, mapDispatchToProps)(Link);
    

    测试:

    import …
    import { Link, mapDispatchToProps, mapStateToProps } from './Link'
    
    test('renders correctly', () => {
      const tree = renderer.create(
        <Link linkInfo="Link info" />
      ).toJSON()
    
      expect(tree).toMatchSnapshot()
    })
    
    test('calls getLinkInfo', () => {
      const getLinkInfo = jest.fn()
    
      const tree = renderer.create(
        <Link getLinkInfo={getLinkInfo} />
      )
    
      tree.props.onMouseEnter()
    
      expect(getLinkInfo).toHaveBeenCalled()
    })
    
    test('mapDispatchToProps', () => … )
    test('mapStateToProps', () => … )
    

    这是纯组件的完整测试。

    问题的第二部分是关于测试异步动作创建者。棘手的部分是axios。它从何而来?我假设你将它导入顶部。所以你必须嘲笑它​​ - 呃,很快就会变得混乱。

    有一个较小的知道extraArgument你可以传递给redux thunk。这可以作为纯依赖注入工作,这使得动作创建者很容易测试。

    像这样使用:

    const store = createStore(
      reducer,
      applyMiddleware(thunk.withExtraArgument({ axios }))
    )
    

    然后将此依赖项(或更多,如果需要)作为第三个参数传递给thunk:

    export function getLinkInfo() {
      return function(dispatch, getState, { axios }) {
        return axios.get('/api/get-link-info')
        .then(response => {
            dispatch(getLinkInfoSuccess(response.data));
            return response;
        });
      };
    }
    

    现在很酷。异步动作创建者的测试:

    import * as actions from './actions'
    
    describe('getLinkInfo', () => {
      const action = actions. getLinkInfo()
    
      const dispatch = jest.fn()
      const getState = () => { … }
      const axios = {
        get: jest.fn(() => Promise.resolve({
          data: {}
        }))
      }
    
      beforeEach(() => {
        deps.axios.get.mockClear()
      })
    
      test('fetches info from the server', () => {
        action(dispatch, getState, { axios })
    
        expect(axios.get).toHaveBeenCalledTimes(1)
        expect(axios.get.mock.calls).toMatchSnapshot()
      })
    
    })
    

    P.S。我在这里展示了这些和一些更好的Jest测试模式:  https://github.com/robinpokorny/jest-example-hannoverjs