【问题标题】:How to test a React component that dispatches a Redux / Thunk action如何测试调度 Redux / Thunk 操作的 React 组件
【发布时间】:2019-11-13 11:37:32
【问题描述】:

我正在为一个组件编写集成测试,该组件应根据异步(thunk)redux 操作的响应重定向到特定路径。

这是我的组件的简化版本:

class MyComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      redirect: false
    }

    this.props.dispatch(asyncThunkAction())
      .then( () => this.setState({redirec: true}) )
      .catch( (err) => console.log('action failed') )
  }

 ...

  render() {

    if (this.state.redirect) {
      return <Redirect to='/whocares' />
    }
    ...
  }
}

function mapStateToProps(state) {
  return {
     ...
  };
}

export default connect(mapStateToProps)(MyComponent);

我想编写一个测试,断言组件重定向到预期的路径。

我正在使用this technique 来检查实际的重定向路径(它并不完美,但它不是这个问题的重点)。

我卡住的地方是redux/thunk动作之后.then()中的状态变化。因为它是一个承诺,所以重定向发生在我的 expect 语句之后,所以我无法测试它。

这是我的测试的样子:

const middlewares = [thunk];
const mockStore = configureStore(middlewares);

  test('redirects after thunk action', () => {
    const redirectUrl = '/whocares'
    const data = {};


    jest.mock('../actions');

    act(() => {
      ReactDOM.render(
        <TestRouter
            ComponentWithRedirection={<MyComponent store={mockStore(data)} />}
            RedirectUrl={redirectUrl}
        />, 
        container);
    });
    expect(container.innerHTML).toEqual(
      expect.stringContaining(redirectUrl)
    )
  })

我的 TestRouter 只是将预期的重定向 URL 打印到 DOM 中。 (查看上面的链接以获取有关此 hack 的完整说明。)因此,我的测试(正确地)识别出在 thunk 操作正在进行时出现的加载屏幕,而不是到达预期的路线。

认为这样做的正确方法是模拟来自asyncThunkAction 的响应,以便它返回具有匹配数据的已解决承诺,但到目前为止我还没有弄清楚如何要做到这一点。我按照manual mocks 上的 Jest 文档创建了相应的模拟文件:

// __mocks__/actions.js
const asyncThunkAction = function(){
    return Promise.resolve({foo: 'bar'});
};
export { asyncThunkAction };

...但我的测试仍然“看到”加载状态。我什至不认为它正在查看我的模拟文件/操作。

这样做的正确方法是什么?

【问题讨论】:

    标签: javascript reactjs unit-testing redux jestjs


    【解决方案1】:

    这是我如何让这项工作发挥作用的“秘诀”...

    使用testing-library/react...

    import { render, fireEvent, waitForElement, act } from '@testing-library/react';
    

    (为这个建议+1 给@tmahle)

    通过创建一个“manual mock”来模拟 axios(或者在我的情况下是包装它的 API 模块),这基本上需要在包含同名文件的真实文件旁边创建一个 __mocks__ 目录。然后导出具有替换 get 方法(或您的代码使用的任何一个)的属性的对象。

    //__mocks__/myclient.js
    export default {
      get: jest.fn(() => Promise.resolve({ data: {} }))
    };
    

    即使你没有在测试中调用模拟代码,你也需要在测试文件中import它...

    import myapi from '../api/myapi';
    jest.mock('../api/myai');
    

    您可以像这样模拟来自模拟 API 调用的响应:

    myapi.get.mockResolvedValueOnce({
      data: { foo: "bar" },
    });
    

    我对这部分有点模糊...... 即使模拟的 API 请求立即响应已解决的承诺,您可能需要wait 才能写expects

    const { getByText, getByTestId, container } = render(<MyComponent />);
    await wait(() => getByText('Some text that appears after the '));
    expect(container.innerHTML).toEqual('whatever');
    

    所有这些都在各种文档和 SO 问题中“存在”...但我花了很长时间才将它们拼凑在一起。希望这可以节省您的时间。

    【讨论】:

      【解决方案2】:

      诚然,这是对您问题的一个侧面回答,但我建议您尝试测试库及其体现的理想,特别是对于集成测试。

      它在 DOM 和 React 两种风格中都可用,使用哪一种可能取决于你的重定向发生在什么抽象级别:

      https://github.com/testing-library/dom-testing-library https://github.com/testing-library/react-testing-library

      使用这种范例,您不会试图断言用户被重定向到正确的路径,而是在他们被重定向后正确的东西在屏幕上。您还将模拟限制在绝对必要的部分(如果您正在进行真正的集成测试,您的测试环境可能无法模拟任何东西或只有浏览器 API)。

      这里的整体方法可能会让您模拟更少,并可能渲染应用程序的更大部分。可以在此处找到一个可能有用的示例:https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?fontsize=14&module=%2Fsrc%2F__tests__%2Freact-router.js&previewwindow=tests

      因为这种方法的模拟较少,所以关于如何实现这一点的具体细节可能来自您给出的示例范围之外,但上面的示例链接应该对入门有很大帮助。

      【讨论】:

      • 我对这个库很熟悉,并且愿意使用它。但是你能提供一些关于如何的细节吗?我之前已经阅读过文档,但我仍然卡住了。
      • 更新了最有用示例的链接。如果您需要更多帮助,我认为为了直接回答如何在您的情况下完成此操作,可能需要包含更多源代码的存储库。
      猜你喜欢
      • 2019-10-23
      • 1970-01-01
      • 2017-06-18
      • 1970-01-01
      • 2018-11-27
      • 2016-06-06
      • 2017-03-03
      • 2019-04-06
      • 2016-11-23
      相关资源
      最近更新 更多