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