测试期间无法使用MemoryRouter导航到路径

时间:2020-06-08 15:53:33

标签: react-router jestjs enzyme

我已经密切关注了这些示例,但是我无法使用 MemoryRouter (这是您应该如何测试路由组件的方法?)来进行使用笑话和酶的测试。

我想导航到其中一条路线,并将其反映在快照中。下面的代码尝试使用MemoryRouter导航到“ / A”,所以我假设我会看到<div>A</div>

import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';

Enzyme.configure({adapter: new Adapter()});

describe('Routing test', () => {
    let wrapper;

    beforeEach(() => {
        wrapper = mount(
            <MemoryRouter initialEntries={["/A"]}>
                    <div className={"Test"}>This is my Test Component and should not have any test specific code in it
                        <Router>
                            <Switch>
                                <Route path={"/A"}>
                                    <div className={"A"}>A</div>
                                </Route>
                                <Route path={"/B"}>
                                    <div>B</div>
                                </Route>
                            </Switch>
                        </Router>
                    </div>
                </MemoryRouter>
        );
    });
    afterEach(() => {
        wrapper.unmount();
    });

    it('matches snapshot', () => {
        expect(wrapper.find(".Test")).toHaveLength(1); //this ok
        expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find  A
    });
});

我没有看到<div>Test<div>A</div></div>,而是看到<div>Test</div>

注意::我的示例简化为一类。我的现实情况是<div>Test...</div>是一个独立的组件。

4 个答案:

答案 0 :(得分:2)

根据documentation,如果您在测试中使用常规的Router,则应向其传递一个history道具

虽然您可能会自己尝试使用路由器上下文,但是我们建议您将单元测试包装在以下路由器组件之一中:带有历史记录道具的基础Router<StaticRouter><MemoryRouter><BrowserRouter>

希望这会起作用。如果没有,也许使用第二个MemoryRouter而不是Router就可以完成这项工作。

答案 1 :(得分:1)

  1. 我找不到任何证据,但是我总是给人留下深刻的印象,因为您只应在树的顶部某处仅使用一个<Router>且不应嵌套它们。
    因此,我自己查看了源代码,如果我做对了,那就是对的。因为:

    1. 反应路由器使用 Context API 将属性沿层次结构传递。
    2. 来自React docs

      [...]它将从树中位于其上方的最接近匹配Provider中读取当前上下文值。

    3. <Router>Provider,但不是Consumer,因此它不能从父<Router>窥视道具。
  2. 当人们提倡测试时,他们还提到编写测试会导致可测试性更高的代码,而可测试性更高的代码则更干净。我不会对此争论,我只是要注意,如果您可以编写可测试的代码,那么您也可以编写不可测试的代码。看起来是这样。

    因此,尽管您明确地说了

    其中不应包含任何测试专用代码

    我会怀疑,虽然您可能不应该按照@aquinq的建议使用createMemoryHistory,或专门放置其他内容,并且仅用于测试,但您可以并且应该 >修改您的代码以使其更具可测试性。

    您可以:

    1. 向上移动<Router>。您甚至可以将<App>包裹起来,尽管这可能不适用于您的情况,但这是最简单且建议的方法。但我仍然不明白为什么您不能将<div className={"Test"}>放在<Router>内,反之亦然。
    2. 在测试中,您不应该测试第三方库,而应该测试自己的代码,因此可以提取此代码
      <Switch>
        <Route path={"/A"}>
          <div className={"A"}>A</div>
        </Route>
        <Route path={"/B"}>
          <div>B</div>
        </Route>
      </Switch>
      
      分成一个单独的组件并分别进行测试。
    3. 或者如果我们将两者结合在一起:将<div className={"Test"}>放在<Router>内,将<div className={"Test"}>提取到一个单独的组件中,编写
      wrapper = mount(
        <MemoryRouter initialEntries={["/A"]}>
          <TestDiv/>
        </MemoryRouter>
      )
      
    4. createMemoryHistory本身也是一个有用的功能。在将来的某个时间,您会发现自己正在使用它。在这种情况下,@ aquinq的答案就可以了。
  3. 但是,如果您根本不能/根本不想修改您的代码。然后,您可以作弊一下并尝试这种方法:How to test a component with the <Router> tag inside of it?

答案 2 :(得分:1)

好,我知道了。

这很丑陋,但是您需要创建一个__mocks__目录(在项目的第一级)。 __mocks__似乎文献不多,但这似乎只是个玩笑,这里的所有内容都将在测试时运行,在这里您可以为某些外部库添加模拟存根。

import React from 'react';

const reactRouterDom = require("react-router-dom")
reactRouterDom.BrowserRouter = ({children}) => <div>{children}</div>

module.exports = reactRouterDom

我的测试文件与我的问题相同(我认为):

import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';

Enzyme.configure({adapter: new Adapter()});

describe('Routing test', () => {
    let wrapper;

    beforeEach(() => {
        wrapper = mount(
            <MemoryRouter initialEntries={['/A']}>
                    <div className={"Test"}>This is my Test Component and should not have any test specific code in it
                        <Router>
                            <Switch>
                                <Route path={"/A"}>
                                    <div className={"A"}>A</div>
                                </Route>
                                <Route path={"/B"}>
                                    <div>B</div>
                                </Route>
                            </Switch>
                        </Router>
                    </div>
                </MemoryRouter>
        );
    });
    afterEach(() => {
        wrapper.unmount();
    });

    it('matches snapshot', () => {
        expect(wrapper.find(".Test")).toHaveLength(1); //this ok
        expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find  A
    });
});

这有效,我的测试呈绿色! :)

更新:

我觉得有些困惑,因为我实际上像对待redux Provider之类的顶级组件一样对待Router。路由器不应像这样位于App内,而应位于App外(例如,在 index.js 文件中)。

ReactDOM.render(
    <Provider store={store}>
        <Router>
            <App/>,
        </Router>
    </Provider>,
    document.getElementById('root')
);

现在,当针对App编写测试时,我提供了自己的路由器,例如 MemoryRouter

答案 3 :(得分:1)

通常,路由器将不在应用程序逻辑之内,如果您使用其他<Route>标签,则可以使用类似<Switch>这样的东西:

  <Router>
    <Switch>
      <Route exact path="/">
        <HomePage />
      </Route>
      <Route path="/blog">
        <BlogPost />
      </Route>
    </Switch>
  </Router>

MemoryRouter实际上是 Router,因此最好用替换“真实” Router 。您可以将其拆分为一个单独的组件,以便于测试。

根据source GitHub

使用低级<Router>的最常见用例是 将自定义历史记录与状态管理库(例如Redux或Mobx)同步。请注意,不需要将状态管理库与React Router一起使用,仅用于深度集成。

import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";

const history = createBrowserHistory();

ReactDOM.render(
  <Router history={history}>
    <App />
  </Router>,
  node
);

根据个人经验:

我使用了外部组件(我们称其为“根”),该组件在顶层包含<Provider><Router>组件,然后<App>仅包含{{1 }}和<Switch>组件。

<Route>返回:

Root.jsx

<Provider store={rootStore}> <Router history={rootHistory}> <App /> </Router> </Provider> 返回:

App.jsx

这允许<Switch> <Route exact path="/" component={HomePage}> <Route exact path="/admin" component={AdminPage}> </Switch> 使用:

App.test.jsx