【问题标题】:How to test a React component with RouteComponentProps?如何使用 RouteComponentProps 测试 React 组件?
【发布时间】:2018-04-06 05:55:07
【问题描述】:

我有一个组件具有扩展 RouteComponentProps 的 props,如下所示:

export interface RouteComponentProps<P> {
  match: match<P>;
  location: H.Location;
  history: H.History;
  staticContext?: any;
}

现在,当我在应用程序中使用我的组件时,我将这些道具传递给它:

<MyComponent
    match={this.props.match}
    location={this.props.location}
    history={this.props.history}
/>

props 已经可用,因为它在 react 路由器中运行。

现在,如果没有matchlocationhistory 可用,我该如何测试这个组件?

我需要模拟它们还是应该使用一些辅助函数以某种方式自动加载它们?

【问题讨论】:

    标签: reactjs typescript testing react-router jestjs


    【解决方案1】:

    一位名叫 Timmy Huang 的先生提供了一个解决方案,其中涉及一个简单的模拟......

    https://spectrum.chat/react/help/how-do-you-test-components-that-use-routecomponentprops~495fe95b-6925-4e7f-bfe8-65a737c5d24e?m=MTU4Mjk1MjQ4ODQ0MA==

    const routeComponentPropsMock = {
      history: {} as any,
      location: {} as any,
      match: {} as any,
    }
    

    我使用 Jest 进行了尝试,它成功了。我的组件有这个签名...

    export const MyComponent: React.FC<RouteComponentProps> = ({location}:RouteComponentProps) => {
    

    我确认组件加载的基本测试看起来像这样......

    function renderMyComponent() {
      return render(
        <MyComponent {...routeComponentPropsMock}/>
      );
    }
    

    【讨论】:

      【解决方案2】:

      要回答您的最后一个问题,推荐的方法是在您的测试中使用&lt;MemoryRouter&gt;&lt; *your component here* &gt;&lt;/MemoryRouter&gt;。 Typescript 没有发现该组件会将所需的道具传递给您的组件,因此我认为它不是是一种类型安全的方法。

      这适用于 React Router v4,不适用于以前的版本。

      对于测试使用 HOC withRouter 包装的组件的类型安全方法,您可以从 react-routerhistory 包构建位置、历史记录和匹配项。

      此示例使用酶和快照测试,但也可以轻松用于任何其他测试。

      这避免了我需要使用 &lt;MemoryRouter&gt; 作为 typescript 无论如何都不喜欢的包装器。

      // Other imports here
      import { createMemoryHistory, createLocation } from 'history';
      import { match } from 'react-router';
      
      const history = createMemoryHistory();
      const path = `/route/:id`;
      
      const match: match<{ id: string }> = {
          isExact: false,
          path,
          url: path.replace(':id', '1'),
          params: { id: "1" }
      };
      
      const location = createLocation(match.url);
      
      test('shallow render', () => {
          const wrapper = shallow(
              <MyComponent history={history}
                           location={location}
                           match={match} />
          );
      
          expect(wrapper).toMatchSnapshot();
      });
      

      注意不要用它来测试实现细节,它可能很诱人,但如果你想重构它会给你带来很多痛苦。

      为此创建一个助手可能是使其可重用的最佳方法。

      import { createLocation, createMemoryHistory } from 'history';
      import { match as routerMatch } from 'react-router';
      
      type MatchParameter<Params> = { [K in keyof Params]?: string };
      
      export const routerTestProps = <Params extends MatchParameter<Params> = {}>
          (path: string, params: Params, extendMatch: Partial<routerMatch<any>> = {}) => {
              const match: routerMatch<Params> = Object.assign({}, {
                  isExact: false,
                  path,
                  url: generateUrl(path, params),
                  params
              }, extendMatch);
              const history = createMemoryHistory();
              const location = createLocation(match.url);
      
              return { history, location, match };
          };
      
      
      const generateUrl = <Params extends MatchParameter<Params>>
          (path: string, params: Params): string => {
              let tempPath = path;
      
              for (const param in params) {
                  if (params.hasOwnProperty(param)) {
                      const value = params[param];
                      tempPath = tempPath.replace(
                          `:${param}`, value as NonNullable<typeof value>
                      );
                  }
              }
      
              return tempPath;
          };
      

      现在我们可以在测试中使用routerTestProps 函数

      const { history, location, match } = routerTestProps('/route/:id', { id: '1' });
      

      【讨论】:

      • v5 of history 不再有 createLocation
      【解决方案3】:

      我一直在寻找一个好的解决方案。我希望我可以在 mapStateToProps 函数或类似的东西中做到这一点,但还没有做到这一点。

      我能做的最好的就是模拟出来并传递比赛、地点和历史。我使用了以下内容:

      import { RouteComponentProps } from 'react-router'
      import { match } from 'react-router-dom';
      import {UnregisterCallback, Href} from 'history'
      
      export function getMockRouterProps<P>(data: P) {
      
          var location: {
                  hash: "",
                  key: "",
                  pathname: "",
                  search: "",
                  state: {}
              };
      
          var props: RouteComponentProps<P> = {
          match: {
                  isExact: true,
                  params: data,
                  path: "",
                  url: ""
              },
              location: location,
              history: {
                  length:2,
                  action:"POP",
                  location: location,
                  push: () => {},
                  replace: () => {},
                  go: (num) => {},
                  goBack: () => {},
                  goForward: () => {},
                  block: (t) => {
                      var temp: UnregisterCallback = null;
                      return temp;
                  },
                  createHref: (t) => {
                      var temp: Href = "";
                      return temp;
                  },
                  listen: (t) => {
                      var temp: UnregisterCallback = null;
                      return temp;
                  }
      
              },
              staticContext: {
              }
          };
      
      
          return props;
      }
      

      然后在我的测试中我做了:

          var routerProps = getMockRouterProps<ReduxTestComponentProps>(null);
      
          const wrapper = mount<ReduxTestComponent, ReduxTestComponentState>(
                  <ReduxTestComponent
                      history={routerProps.history}
                      location={routerProps.location}
                      match={routerProps.match}
                      isLoadingTodo={false}
                      todos={todos}
                      addAsyncTodoActionDispatch={() => mockTodoAddDispatch()}
                      deleteTodoActionDispatch={() => mockTodoDeleteDispatch()}
                      />
            );
      

      【讨论】:

        猜你喜欢
        • 2020-05-16
        • 2021-10-24
        • 1970-01-01
        • 1970-01-01
        • 2017-01-01
        • 2021-04-25
        • 2021-12-06
        • 2020-04-26
        • 1970-01-01
        相关资源
        最近更新 更多