react-router 路由中基于路由的测试的推荐方法

IT技术 reactjs unit-testing jestjs integration-testing react-testing-library
2021-03-29 03:37:26

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

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

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

这是一个人为的例子:

应用程序.tsx

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

拒绝访问.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 组件在其内部定义了路由,而 myAccessDenied只是这些路由之一。但是,是否可以App.tsx在我的AccessDenied.test.tsx测试中利用 my定义的路由器也许我错误地解决了这个问题?这就是我挣扎的地方。作为参考,这是我的工作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()
  })
})

任何想法或建议表示赞赏!

1个回答

如果考虑AccessDenied组件的责任,实际上并不是将用户送回家这是您想要的整体行为,但组件在其中的作用只是将用户发送到"/". 因此,在组件单元级别,测试可能如下所示:

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"例如,如果您将主页移至,此测试将继续通过,但应用程序将不再实际工作。这是过度依赖非常低级测试的常见问题,也是高级测试发挥作用的地方,包括:

  • 集成:渲染整体App并用于fireEvent模拟导航。这在您当前的设置中具有挑战性,因为Router它处于App水平;我会移动Routertoindex.tsx并使用SwitchinApp.tsx代替,因此您可以App在 a 内渲染MemoryRouter或使用createMemoryHistory我上面显示方法(例如,我在此入门套件中已完成此操作)。

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

我还没有展示路由测试,但在我的博客上涵盖了一个简单 React 应用程序的这些不同级别的测试


在 React Router v6 中,您需要Router稍微更新一下用法(详情请参阅在 v6 React Router 中测试页面时的“无法读取未定义的属性(读取 'pathname')”):

render(
  <Router location={history.location} navigator={history}>
    <AccessDenied />
  </Router>
);
@coloradocolby 如果真正的路由器在应用程序内部,则在渲染应用程序时,您无法像我在这里那样轻松地将其换出。如果您将路由器拉到索引文件,您可以在您控制的路由器上下文中渲染任何组件。
2021-06-03 03:37:26
感谢乔恩如此全面的回答!我喜欢通过验证位置路径名来简化组件单元测试的想法。我不确定我是否理解为什么将路由器放在 App 组件中(在我的实际项目中,它实际上是在它自己的嵌套在 App 内部的组件中)会使测试变得更加困难?你介意澄清一下吗?
2021-06-16 03:37:26