简体   繁体   English

如何测试调度 Redux / Thunk 操作的 React 组件

[英]How to test a React component that dispatches a Redux / Thunk action

I'm writing an integration test for a component that should redirect to a specific path depending on the response from an asynchronous (thunk) redux action.我正在为应该重定向到特定路径的组件编写集成测试,具体取决于来自异步(thunk)redux 操作的响应。

This is a simplified version of my component:这是我的组件的简化版本:

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);

I want to write a test that asserts that the component redirected to the expected path.我想编写一个测试,断言组件重定向到预期的路径。

I am using this technique for inspecting the actual redirection path (It's not perfect but it's not the focus of this question).我正在使用这种技术来检查实际的重定向路径(它并不完美,但它不是这个问题的重点)。

The place where I am stuck is the state change in the .then() following the redux/thunk action.我被卡住的地方是在 redux/thunk 操作之后 .then .then()中的 state 更改。 Because it's a promise, the redirect happens after my expect statement, so I have not been able to test that.因为它是 promise,重定向发生在我的expect语句之后,所以我无法测试它。

Here's what my test looks like:这是我的测试的样子:

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)
    )
  })

My TestRouter just prints the anticipated redirect URL into the DOM.我的 TestRouter 只是将预期的重定向 URL 打印到 DOM 中。 (Check out the link above for a full explanation of this hack.) So right now instead of hitting the expected route, my test (correctly) identifies the loading screen that appears while the thunk action is in progress. (查看上面的链接以获取有关此 hack 的完整说明。)因此,现在我的测试(正确地)识别了在 thunk 操作正在进行时出现的加载屏幕,而不是到达预期的路线。

I think the right way to do this is to mock the response from asyncThunkAction so that it returns a resolved promise with matching data, but so far I have not been able to figure out how to do that.认为正确的方法是模拟来自asyncThunkAction的响应,以便它返回具有匹配数据的已解析 promise,但到目前为止我还没有弄清楚如何做到这一点。 I followed the Jest documentation on manual mocks and created the corresponding mock file:我按照手动模拟的 Jest 文档并创建了相应的模拟文件:

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

...but my test still "sees" the loading state. ...但我的测试仍然“看到”加载 state。 I don't even think it's looking at my mocked file/action.我什至不认为它正在查看我的模拟文件/动作。

What is the right way to do this?这样做的正确方法是什么?

Here's my "recipe" for how I was able to get this working...这是我如何让这个工作的“食谱”......

Use testing-library/react ...使用测试库/反应...

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

(+1 to @tmahle for this suggestion) (为此建议向@tmahle +1)

Mock axios (or in my case the API module that wraps it) by creating a " manual mock " which basically entails creating a __mocks__ directory next to the real file containing a file by the same name.通过创建一个“手动模拟”来模拟 axios(或者在我的情况下是包装它的 API 模块),这基本上需要在包含同名文件的真实文件旁边创建一个__mocks__目录。 Then export an object with a property that replaces the get method (or whichever one your code uses).然后导出一个 object ,其属性替换get方法(或您的代码使用的任何一个)。

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

Even if you don't call the mocked code in your test, you need to import it in the test file...即使你没有在测试中调用模拟代码,你也需要在测试文件中import它......

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

You can mock the response from the mocked API call like this:您可以像这样模拟来自模拟的 API 调用的响应:

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

I'm a little fuzzy on this part... Even though the mocked API request responds immediately with a resolved promise, you probably need to wait for it to write expects我在这部分有点模糊......即使模拟的 API 请求立即响应已解决的 promise,您可能需要wait它写入expects

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

All of this was "out there" in various docs and SO questions... but it took me a long time to cobble it all together.所有这些都在各种文档和 SO 问题中“存在”……但我花了很长时间才将它们拼凑在一起。 Hopefully this saves you time.希望这可以节省您的时间。

This is a little bit of a sideways answer to your question, admittedly, but I would recommend trying out testing-library and the ideals that it embodies, especially for integration tests.诚然,这是对您的问题的一个侧面回答,但我建议尝试测试库及其体现的理想,特别是对于集成测试。

It is available in both DOM and React flavors, which one to use likely depends on what level of abstraction your redirect is happening at:它在 DOM 和 React 两种风格中都可用,使用哪一种可能取决于你的重定向发生在什么抽象级别:

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

With this paradigm you would not try to assert that the user gets redirected to the correct path, but rather that the correct thing is on the screen after they are redirected.使用这种范例,您不会试图断言用户被重定向到正确的路径,而是在他们被重定向后正确的东西在屏幕上。 You would also limit your mocking to the absolutely bare necessities (likely nothing or only browser API's that your test environment cannot emulate if you are doing a true integration test).您还将 mocking 限制为绝对必需品(如果您正在进行真正的集成测试,可能没有或只有浏览器 API 是您的测试环境无法模拟的)。

The overall approach here would probably have you mocking out much less and perhaps rendering a larger portion of the app.这里的整体方法可能会让你的 mocking 输出更少,并且可能渲染应用程序的更大部分。 A likely-helpful example to draw from can be found here: https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?fontsize=14&module=%2Fsrc%2F__tests__%2Freact-router.js&previewwindow=tests可以在这里找到一个可能有用的示例: https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?fontsize=14&module=%2Fsrc%2F__tests__%2Freact -router.js&previewwindow=测试

Because there's less mocking in this approach, the specifics for how you can accomplish this would likely come from outside the scope of the example you've given, but the above example link should help a lot with getting started.因为这种方法中的 mocking 较少,所以如何实现这一点的细节可能来自您给出的示例的 scope 之外,但上面的示例链接应该对入门有很大帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM