react-router 路由中基于路由的测试的推荐方法 [英] Recommended approach for route-based tests within routes of react-router

查看:33
本文介绍了react-router 路由中基于路由的测试的推荐方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

这是一个人为的例子:

App.tsx

<代码><><路由器><路由精确路径="/";组件={Home}/><路由精确路径="/access-denied";组件={AccessDenied}/></路由器><页脚/></>

拒绝访问.tsx

<div>拒绝访问</div><p>您无权查看请求的页面</p><链接到=/"><button>Go Home</button><---这是我想要测试的</链接>

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

App.test.tsx

describe('App', () => {it('应该允许你导航到登录', async () => {const 历史 = createMemoryHistory()const { findByTestId, getByTestId } = render(<MockedProvider mocks={mocks} addTypename={false}><AuthContext.Provider价值={{authState: AUTH_STATES.UNAUTHENTICATED,}}><路由器历史={历史}><应用程序/></路由器></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 组件的责任,实际上并不是将用户送回家em>.这是您想要的整体行为,但组件在其中的作用只是将用户发送到 "/".因此,在组件单元级别,测试可能如下所示:

import React, { FC } from "react";import { Link, Router } from react-router-dom";从@testing-library/react"导入 { fireEvent, render, screen };import { createMemoryHistory } from "history";const AccessDenied: FC = () =>(<div><div>拒绝访问</div><p>您无权查看请求的页面</p><链接到=/"><button>Go Home</button></链接>

);描述(拒绝访问",()=> {it(将用户送回家", () => {const history = createMemoryHistory({ initialEntries: ["/access-denied"] });使成为(<路由器历史={历史}><拒绝访问/></路由器>);fireEvent.click(screen.getByText(回家"));期望(history.location.pathname).toBe("/");});});

注意 "/" 是默认路径,所以如果您不提供 initialEntries 测试通过即使点击没有做任何事情......

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

  • 集成:渲染整个App并使用fireEvent来模拟导航.这在您当前的设置中具有挑战性,因为 Router 位于 App 级别;我会将 Router 移动到 index.tsx 并在 App.tsx 中有一个 Switch 代替,所以你可以在 MemoryRouter 中渲染 App 或使用我上面展示的 createMemoryHistory 方法(我已经在 这个入门工具包 例如).

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

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


在 React Router v6 中,您需要稍微更新 Router 用法(参见 无法读取属性未定义(读取路径名")"在 v6 React Router 中测试页面时 以获取详细信息):

render(<路由器位置={history.location} navigator={history}><拒绝访问/></路由器>);

I'm using react-testing-library within a project of mine and am trying to write tests that validate in-app routing.

e.g. testing that a button on the AccessDenied page brings you back to the Home page.

I've been able to write these sorts of tests successfully for my App component because it defines all of the app routes. But if AccessDenied is one of those routes, how do I need to set up my tests to validate a button clicked there will route my back to Home?

Here is a contrived example:

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>

As I said earlier the reason my tests work inside App.test.tsx is because my App component defines the routes inside itself, whereas my AccessDenied is just one of those routes. However, is it possible to leverage the router defined in my App.tsx in my AccessDenied.test.tsx tests? Perhaps I'm approaching this problem incorrectly? That's where I'm struggling. For reference, here is my working App.test.tsx tests.

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()
  })
})

Any thoughts or suggestions are appreciated!

解决方案

If you think about the responsibility of the AccessDenied component, it isn't really to send the user home. That's the overall behaviour you want, but the component's role in that is simply to send the user to "/". At the component unit level, therefore, the test could look something like this:

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("/");
    });
});

Note that "/" is the default path, so if you don't provide initialEntries the test passes even if the click doesn't do anything...

At that point you might be thinking "but what if the home route changes?" If you moved the home page to "/home", for example, this test would continue to pass but the application would no longer actually work. This is a common problem with relying too much on very low-level tests and is where higher-level tests come into play, including:

  • Integration: render the whole App and use fireEvent to simulate navigation. This is challenging in your current setup, because the Router is at the App level; I'd move the Router to index.tsx and have a Switch in App.tsx instead, so you can render App within a MemoryRouter or use the createMemoryHistory method I show above (I've done this in this starter kit for example).

  • End-to-end: use a browser driver (e.g. Cypress or the various Selenium-based options) to automate actual user interactions with the app.

I haven't got as far as showing tests for routing, but do cover these different levels of test for a simple React app on my blog.


In React Router v6, you need to update the Router usage slightly (see "Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router for details):

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

这篇关于react-router 路由中基于路由的测试的推荐方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆