在 react-router 的路由内进行基于路由的测试的推荐方法

Recommended approach for route-based tests within routes of react-router

我正在我的一个项目中使用 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()
  })
})

如有任何想法或建议,我们将不胜感激!

想想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 即使单击不执行任何操作...

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

  • Integration:渲染整个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 用法(详见 ):

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