如何使用RouteComponentProps测试React组件?

时间:2017-10-25 08:20:25

标签: reactjs typescript testing react-router jestjs

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

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

道具已经可用,因为它在反应路由器内部运行。

现在,如何在没有matchlocationhistory的情况下测试此组件?

我是否需要模拟它们,或者是否应该以某种方式自动加载一些辅助函数?

4 个答案:

答案 0 :(得分:6)

如果您使用的是Jest(或其他)模拟库,则可以提供一个简单的模拟,并且测试将通过-以下示例使用了jest:

test('renders without crashing', () => {

    let mock: any = jest.fn();

    const wrapper = enzyme.mount(
        <TeamSetupApp
            match = {mock}
            location = {mock}
            history= {mock} />
      );

    expect(wrapper).not.toBeNull();
});

很显然,如果您想在测试中使用匹配,位置或历史记录的元素来使代码遵循特定的路径,则匹配/位置/历史记录应根据需要分别进行模拟。

答案 1 :(得分:3)

要回答您的最后一个问题,建议的方法是在测试中使用<MemoryRouter>< *your component here* ></MemoryRouter>。 Typescript不会接受此组件会将所需的属性传递给您的组件,因此我认为是一种类型安全的方法。

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

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

此示例使用酶和快照测试,但可以像其他任何测试一样容易。

这避免了我需要使用<MemoryRouter>作为打字稿不喜欢的包装器。

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

答案 2 :(得分:3)

一位名叫蒂米·黄(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}/>
  );
}

答案 3 :(得分:2)

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