【问题标题】:Cannot navigate to path using MemoryRouter while testing测试时无法使用 MemoryRouter 导航到路径
【发布时间】:2020-09-27 15:47:36
【问题描述】:

我已经密切关注示例,但我无法让 MemoryRouter(您应该如何测试路由组件?)使用 jest 和酶进行测试。

我想导航到其中一条路线,并将其反映在我的快照中。下面的代码尝试使用 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
    });
});

我只看到&lt;div&gt;Test&lt;/div&gt;而不是&lt;div&gt;Test&lt;div&gt;A&lt;/div&gt;&lt;/div&gt;

注意:我的示例被简化为一类。我的真实情况是&lt;div&gt;Test...&lt;/div&gt; 是一个单独的组件。

【问题讨论】:

    标签: react-router jestjs enzyme


    【解决方案1】:

    通常路由器将在应用程序逻辑之外,如果您使用其他 &lt;Route&gt; 标签,那么您可以使用类似 &lt;Switch&gt; 的标签,如下所示:

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

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

    根据source GitHub

    使用低级 &lt;Router&gt; 的最常见用例是 将自定义历史记录与 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
    );
    

    个人经验:

    我使用了一个外部组件(我们称之为“根”),它在顶层包含&lt;Provider&gt;&lt;Router&gt; 组件,然后&lt;App&gt; 只包含&lt;Switch&gt;&lt;Route&gt; 组件。

    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 使用:

    mount(
      <Provider store={fakeStore}>
        <MemoryRouter initialEntries={['/']}>
          <App myProp={dummyProp} />
        </MemoryRouter>
      </Provider>
    )
    

    【讨论】:

      【解决方案2】:

      好的,我想通了。

      它非常难看,但您需要创建一个__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 这样的顶级组件。路由器不应位于 App 内部,而应位于 App 外部(例如在 index.js 文件中)。

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

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

      【讨论】:

      【解决方案3】:
      1. 我找不到任何证据,但我一直觉得你应该在树顶的某个地方只使用一个 &lt;Router&gt; 并且不应该嵌套它们。
        所以我自己查看了源代码,如果我做对了,这是真的。因为:

        1. react-router 使用 Context API 将 props 向下传递。
        2. 来自React docs

          [...] 它将从树中匹配Provider最近 读取当前上下文值。

        3. &lt;Router&gt;Provider 但不是 Consumer,因此它无法从父 &lt;Router&gt; 处查看道具
      2. 当人们提倡测试时,他们还提到编写测试会产生更可测试的代码,并且更可测试的代码更干净。我不会为此争论,我只是不想指出,如果您可以编写可测试的代码,那么您也可以编写不可测试的代码。看起来就是这样。

        所以虽然你特意这么说

        其中不应包含任何测试特定代码

        我认为,虽然您可能不应该像 @aquinq 建议的那样使用 createMemoryHistory,或者专门添加任何其他内容并且仅用于测试目的,但您可以并且可能应该修改 你的代码更易于测试。

        你可以:

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

      【讨论】:

      • 请理解 和我的意思一样。我将整个原始组件放在测试用例中,以便更好地说明它。有趣的点,但如果你能通过我的测试,我会给你我的赏金。
      • @OliverWatkins,所以,我不明白:您已经按照我发布的链接的建议回答了自己的问题。但是,当您在答案中留下此评论和“:/”时,我也不知道如何弄清楚您的答案和评论的确切时间......问题是否得到了回答?
      • 是的,我知道&lt;TestDiv&gt; 是有问题的组件,并且知道我建议更改它...或使用链接中的建议,您这样做了,没关系。
      • 它回答了一半,我会给你一半的赏金。 :/ 意味着我现在必须弄清楚如何在测试中交互式导航路线。还是谢谢
      • 这就是我所说的:/ - 即。我还有一个问题:stackoverflow.com/questions/62411156/…
      【解决方案4】:

      根据documentation,如果你在测试中使用普通的Router,你应该传递一个history prop给它

      虽然您可能很想自己存根路由器上下文,但我们建议您将单元测试包装在其中一个路由器组件中:基础 Router 与历史道具,或 &lt;StaticRouter&gt;&lt;MemoryRouter&gt;、或&lt;BrowserRouter&gt;

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

      【讨论】:

      • 不完全确定将历史记录传递给路由器的意思
      • 这意味着你需要定义你的Router的历史
      • import { createMemoryHistory } from 'history';
      • &lt;Router history={createMemoryHistory({ initialEntries: ['/'] })}&gt;...&lt;/Router&gt;
      • 在上面的例子中,我用 替换了 MemoryRouter ..就像你说的,但我的测试仍然没有通过.
      猜你喜欢
      • 2018-09-04
      • 2017-10-05
      • 2017-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-08
      • 2020-12-27
      • 2021-10-04
      相关资源
      最近更新 更多