reactjs 如何使用RouteComponentProps测试React组件?

qgzx9mmu  于 2022-12-18  发布在  React
关注(0)|答案(4)|浏览(157)

我有一个组件,它包含扩展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}
/>

prop 已经可用,因为它是在react路由器内部运行的。
现在,如果没有matchlocationhistory,我该如何测试这个组件呢?
我需要模拟它们吗?或者它应该以某种方式自动加载一些帮助函数吗?

3gtaxfhh

3gtaxfhh1#

为了回答你的最后一个问题,推荐的方法是在你的测试中使用<MemoryRouter>< *your component here* ></MemoryRouter>。Typescript没有发现这个组件会把所需的属性传递给你的组件,因此我认为它不是类型安全的方法。

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

要使用类型安全的方法来测试用HOC withRouter Package 的组件,您可以从react-routerhistory包构建位置、历史记录和匹配。
本例使用酶和快照测试,但也可以使用任何其他测试。
这避免了我需要使用<MemoryRouter>作为typescript无论如何都不喜欢的 Package 器。

// 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();
});

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

为此创建一个helper可能是使其可重用的最好方法。

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

xoshrz7s

xoshrz7s2#

一位名叫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}/>
  );
}

nbysray5

nbysray53#

我一直在寻找一个很好的解决方案。我希望我可以在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()}
                />
      );
hxzsmxv2

hxzsmxv24#

我使用了类似于another answer的解决方案;使用助手构建 prop 。)我的助手使用path-to-regexpcompile

import { createLocation } from "history";
import { compile } from "path-to-regexp";
import { match } from "react-router";

/**
 * Makes RouteComponentProps.
 *
 * @param path The parameterized react-router path: `/p/:id`.
 * @param params the parameters to substitute in the path: `{id: "123"}`.
 */
export function makeRouteComponentProps<T extends Record<string, string>>(
  path: string,
  params: T
) {
  const toUrl = compile(path, { encode: encodeURIComponent });
  const url = toUrl(params);
  const match: match<T> = {
    isExact: false,
    path,
    url,
    params,
  };
  const location = createLocation(match.url);

  return { match, location };
}

在测试中这样使用它:

const history = createMemoryHistory();
    const { location, match } = makeRouteComponentProps(
      "p/:rootTargetId/:slug",
      {
        rootTargetId: proposition.id,
        slug: toSlug(proposition.text),
      }
    );

    // Act
    const { container } = renderWithProviders(
      <JustificationsPage
        rootTargetType={"PROPOSITION"}
        history={history}
        location={location}
        match={match}
      />,
      { history }
    );

其中renderWithProviders与redux文档中的建议相同:

import React, { PropsWithChildren } from "react";
import { render } from "@testing-library/react";
import type { RenderOptions } from "@testing-library/react";
import type { PreloadedState } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import { Router } from "react-router-dom";
import { createMemoryHistory, History } from "history";
import { persistStore, PersistorOptions } from "redux-persist";

import type { AppStore, RootState } from "./setupStore";
import { setupStore } from "./setupStore";

interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
  preloadedState?: PreloadedState<RootState>;
  store?: AppStore;
  persist?: boolean;
  history?: History<any>;
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    store = setupStore(preloadedState),
    persist = true,
    history = createMemoryHistory(),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  const persistor = persistStore(store, {
    manualPersist: true,
  } as PersistorOptions);
  if (persist) {
    persistor.persist();
  }
  function Wrapper({
    children,
  }: PropsWithChildren<Record<string, unknown>>): JSX.Element {
    return (
      <Provider store={store}>
        <Router history={history}>{children}</Router>
      </Provider>
    );
  }

  return {
    store,
    persistor,
    history,
    ...render(ui, { wrapper: Wrapper, ...renderOptions }),
  };
}


关于history@5中缺少的createLocation,这个评论建议parsePath可以用作替代品,但我没有研究它,因为我仍然在v4上。

相关问题