次のような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}
/>
リアクティブルーター内で実行されているため、小道具は既に利用可能です。
さて、match
、location
、history
を使用せずにこのコンポーネントをテストするにはどうすればよいですか?
それらをモックする必要がありますか、それとも何らかの方法で何らかのヘルパー関数で自動的にロードすることになっていますか?
最後の質問に答えるために、推奨されるアプローチはテストで<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' });
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();
});
明らかに、テストで一致、場所、または履歴の要素を使用してコードを特定のパスにしたい場合、必要に応じて、一致/場所/履歴を個別にモックアウトする必要があります。
私はこれに対する良い解決策を探していました。 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()}
/>
);