【问题标题】:Recommended approach for route-based tests within routes of react-routerreact-router 路由中基于路由的测试的推荐方法
【发布时间】:2021-03-24 00:41:07
【问题描述】:

我在我的一个项目中使用 react-testing-library,并正在尝试编写验证应用内路由的测试。

例如测试 AccessDenied 页面上的按钮是否将您带回主页。

我已经能够为我的 App 组件成功编写这些类型的测试,因为它定义了所有的应用程序路由。但是,如果 AccessDenied 是这些路线之一,我需要如何设置我的测试来验证单击的按钮是否会将我返回主页?

这是一个人为的例子:

App.tsx

<>
  <Router>
    <Route exact path="/" component={Home} />
    <Route exact path="/access-denied" component={AccessDenied} />
  </Router>
  <Footer />
</>

AccessDenied.tsx

<div>
  <div>Access Denied</div>
  <p>You don't have permission to view the requested page</p>
  <Link to="/">
    <button>Go Home</button>    <--- this is what i want tested
  </Link>
</div>

正如我之前所说,我的测试在 App.test.tsx 中工作的原因是因为我的 App 组件在其内部定义了路由,而我的 AccessDenied 只是这些路由之一。但是,是否可以在我的AccessDenied.test.tsx 测试中利用我的App.tsx 中定义的路由器?也许我错误地处理了这个问题?这就是我挣扎的地方。作为参考,这是我的工作 App.test.tsx 测试。

App.test.tsx

describe('App', () => {
  it('should allow you to navigate to login', async () => {
    const history = createMemoryHistory()
    const { findByTestId, getByTestId } = render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <AuthContext.Provider
          value={{
            authState: AUTH_STATES.UNAUTHENTICATED,
          }}
        >
          <Router history={history}>
            <App />
          </Router>
        </AuthContext.Provider>
      </MockedProvider>,
    )

    fireEvent.click(getByTestId('sidebar-login-button'))
    expect(await findByTestId('login-page-login-button')).toBeInTheDocument()

    fireEvent.click(getByTestId('login-page-register-button'))
    expect(await findByTestId('register-page-register-button')).toBeInTheDocument()
  })
})

感谢任何想法或建议!

【问题讨论】:

    标签: reactjs unit-testing jestjs integration-testing react-testing-library


    【解决方案1】:

    如果您考虑AccessDenied 组件的职责,它并不是真正将用户发送到home。这是您想要的整体行为,但组件在其中的作用只是将用户发送到"/"。因此,在组件单元级别,测试可能如下所示:

    import React, { FC } from "react";
    import { Link, Router } from "react-router-dom";
    import { fireEvent, render, screen } from "@testing-library/react";
    import { createMemoryHistory } from "history";
    
    const AccessDenied: FC = () => (
        <div>
            <div>Access Denied</div>
            <p>You don't have permission to view the requested page</p>
            <Link to="/">
                <button>Go Home</button>
            </Link>
        </div>
    );
    
    describe("AccessDenied", () => {
        it("sends the user back home", () => {
            const history = createMemoryHistory({ initialEntries: ["/access-denied"] });
            render(
                <Router history={history}>
                    <AccessDenied />
                </Router>
            );
    
            fireEvent.click(screen.getByText("Go Home"));
    
            expect(history.location.pathname).toBe("/");
        });
    });
    

    注意"/" 是默认路径,因此如果您不提供initialEntries,即使点击没有执行任何操作,测试也会通过... em>

    此时您可能会想“但是如果主页路径发生变化怎么办?” 例如,如果您将主页移至"/home",则此测试将继续通过,但应用程序将不再实际工作。这是过度依赖非常低级测试的常见问题,也是高级测试发挥作用的地方,包括:

    • 集成:渲染整个App并使用fireEvent模拟导航。这在您当前的设置中具有挑战性,因为Router 处于App 级别;我会将Router 移动到index.tsx 并在App.tsx 中有一个Switch,因此您可以在MemoryRouter 内渲染App 或使用我上面显示的createMemoryHistory 方法(我已经例如在this starter kit 中完成此操作)。

    • 端到端:使用浏览器驱动程序(例如 Cypress 或各种基于 Selenium 的选项)自动执行用户与应用的实际交互。

    我还没有展示路由测试,但确实涵盖了简单 React 应用程序的这些不同级别的测试on my blog


    在 React Router v6 中,您需要稍微更新 Router 的用法(详见 "Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router):

    render(
      <Router location={history.location} navigator={history}>
        <AccessDenied />
      </Router>
    );
    

    【讨论】:

    • 感谢 Jon 提供如此全面的答案!我喜欢通过验证位置路径名来简化组件单元测试的想法。我不确定我是否理解为什么将路由器放在 App 组件中(在我的真实项目中,它实际上是在嵌套在 App 内部的自己的组件中)会使测试变得更加困难?你介意澄清一下吗?
    • @coloradocolby 如果真正的路由器在应用程序本身内部,则在渲染应用程序时,您不能像我在这里那样轻松地将其换出。如果您将路由器拉到索引文件,您可以在您控制的路由器上下文中渲染任何组件。
    猜你喜欢
    • 1970-01-01
    • 2015-07-22
    • 2018-09-03
    • 2017-07-17
    • 2016-07-01
    • 1970-01-01
    • 2018-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多