web-dev-qa-db-ja.com

RouteComponentPropsでReactコンポーネントをテストするには?

次のような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を使用せずにこのコンポーネントをテストするにはどうすればよいですか?

それらをモックする必要がありますか、それとも何らかの方法で何らかのヘルパー関数で自動的にロードすることになっていますか?

16
Sergei Basharov

最後の質問に答えるために、推奨されるアプローチはテストで<MemoryRouter>< *your component here* ></MemoryRouter>を使用することです。 TypeScriptは、このコンポーネントがコンポーネントに必要な小道具を渡すことを認識しません。そのため、notはタイプセーフなアプローチであると想定しています。

これはReact Router v4用であり、以前のバージョンには適用されません。

HOC withRouterでラップされたコンポーネントをテストするタイプセーフなメソッドの場合、react-routerおよびhistoryパッケージから場所、履歴、および一致を構築できます。

この例では、酵素とスナップショットのテストを使用していますが、他のテストと同じくらい簡単にできます。

これにより、TypeScriptが好まないラッパーとして<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();
});

[〜#〜] caution [〜#〜]実装の詳細をテストするためにこれを使用しないでください。魅力的かもしれませんが、リファクタリングしたい場合には大きな苦痛をもたらします。

これをヘルパーにすることは、おそらくこれを再利用可能にする最良の方法でしょう。

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

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

明らかに、テストで一致、場所、または履歴の要素を使用してコードを特定のパスにしたい場合、必要に応じて、一致/場所/履歴を個別にモックアウトする必要があります。

6
Farskeptic

私はこれに対する良い解決策を探していました。 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()}
                />
      );
2
Aziz