Hello! 欢迎来到小浪资源网!


测试 ReactJS 上下文 – 测试替身指南


在这篇文章中,我将逐步介绍使用测试库测试依赖于上下文的 react 组件的思维过程。我的目标是探索一种不同的方法来测试这些组件,检查使用模拟与不模拟上下文的测试的优缺点。我们将研究每种方法如何影响测试的可靠性,并且我将分享关于何时以及为什么一种方法在实际应用中可能比另一种方法更有益的见解。

你应该知道什么

  • reactjs 是用来做什么的(可能你已经写过一些应用了)
  • 什么是 vitest

什么是反应上下文

reactjs 上下文的出现是为了解决 reactjs 组件结构中的一个常见问题:道具钻探。当我们有一系列组件需要访问同一组数据时,就会发生道具钻探。上下文机制允许组件共享同一组数据,只要上下文本身是第一个后代。

在reactjs文档中,使用了保存主题的上下文,因为其他组件可能需要此信息,文档使用上下文来处理该信息,而不是通过道具传递值。另一个示例是使用上下文来保存应用程序的布局,在 json-tool 示例中,app.tsx 使用可用于所有应用程序的 defaultlayout 上下文来包装应用程序。

本示例的应用程序

下面的示例将使用主题应用程序。它是一个允许用户在浅色/深色主题之间切换的应用程序。该应用程序也在reactjs官方文档中使用。该应用程序由一个简单的开关组成,可以在浅色主题模式和深色主题模式之间切换。该应用程序非常简单,我们可以将所有内容绘制在一个文件中:

import { createcontext, usecontext, usestate } from 'react' const themecontext = createcontext('light')  function page() {   const theme = usecontext(themecontext)   return (     <div>       <p>current theme: {theme}</p>     </div>   ) }  function app() {   const [theme, settheme] = usestate('light')   return (     <themecontext.provider value={theme}>       <button         classname={theme}         onclick={() => settheme(theme === 'light' ? 'dark' : 'light')}       >         toggle       </button>       <page />     </themecontext.provider>   ) }  export default app 

在这个应用程序中,我们有两个主要组件:app 和 page。 app 组件作为主要组件,包含当前主题的状态,可以是“亮”或“暗”。它还包括一个可在浅色和深色模式之间切换主题的按钮。 page 组件是 app 的子组件,它使用主题上下文来显示当前主题。 app 组件中的按钮是一个简单的切换按钮,单击时会切换主题并相应地更新上下文值。

测试 ReactJS 上下文 – 测试替身指南

在下一节中,我们将讨论对组件进行切片以进行测试。

点火测试

通常在任何应用程序中,我们都必须专注于我们想要做什么样的测试,以及我们想要处理哪个部分。例如,我们可以针对单个组件,而不是整个应用程序。在我们的示例中,我们将从页面组件开始。这将需要我们使用测试替身来测试它。

测试 ReactJS 上下文 – 测试替身指南

测试替身来自应用程序结构本身,因为它取决于上下文,要更改它,上下文中的值也需要更改。

测试双打

为了开始使用 reactjs 中的上下文进行测试方法,我们将开始编写第一个测试:

import { render, screen } from '@testing-library/react' import { page } from './page'  describe('<page />', () => {   it('should render light as default theme', () => {     render(<page />)     expect(screen.getbytext('current theme: light')).tobeinthedocument()   }) }) 

鉴于浅色主题设置为 themecontext 中的默认主题,此测试将按预期通过。我们甚至可以测试第一个示例,但是,当我们对黑暗主题感兴趣时,第二个测试中的事情会变得有趣。为了进入黑暗主题,我们需要开始使用测试替身,因为我们依赖于 reactjs 上下文来做到这一点。第二个测试将 vi.mock 和 vi.mocked 混合在一起。请注意,要编写的第二个测试也需要更改第一个测试。

import { render, screen } from '@testing-library/react' import { page } from './page' import { usecontext } from 'react'  vi.mock('react', () => {   return {     ...vi.importactual('react'),     usecontext: vi.fn(),     createcontext: vi.fn()   } })  describe('<page />', () => {   it('should render light as default theme', () => {     vi.mocked(usecontext).mockreturnvalue('light')     render(<page />)     expect(screen.getbytext('current theme: light')).tobeinthedocument()   })    it('should render dark theme', () => {     vi.mocked(usecontext).mockreturnvalue('dark')     render(<page />)     expect(screen.getbytext('current theme: dark')).tobeinthedocument()   }) }) 

两个测试用例现在都使用假的来测试驱动应用程序。如果我们改变上下文的返回数据,测试也会改变。这里的注意点是:

  • 我们正在嘲笑reactjs上下文,这违背了“不要嘲笑你不拥有的东西”的原则
  • 测试变得更加冗长,因为我们需要使用模拟来做到这一点
  • 我们编写的两个测试没有反映用户与应用程序的交互。我们知道,当按下切换按钮时,主题将会改变。

本节中使用的完整代码可在 github 上获取

没有测试替身

下一个方法是使用嵌入到我们的应用程序中的上下文,而不隔离它或使用任何测试替身。如果我们采用这种 tdd 方法,我们可以从一个非常简单的测试开始,模拟用户的行为方式:

import { render, screen } from '@testing-library/react' import app from './app' import userevent from '@testing-library/user-event'  describe('<app />', () => {   it('should render toggle button', () => {     render(<app />)     expect(screen.getbytext('toggle')).tobeinthedocument()   }) }) 

然后进行第二个测试,我们希望默认设置灯光主题:

import { render, screen } from '@testing-library/react' import app from './app' import userevent from '@testing-library/user-event'  describe('<app />', () => {   it('should render toggle button', () => {     render(<app />)     expect(screen.getbytext('toggle')).tobeinthedocument()   })    it('should render light as default theme', () => {     render(<app />)     expect(screen.getbytext('current theme: light')).tobeinthedocument()   }) }) 

最后但并非最不重要的主题切换:

import { render, screen } from '@testing-library/react' import App from './App' import userEvent from '@testing-library/user-event'  describe('<App />', () => {   it('should render toggle button', () => {     render(<App />)     expect(screen.getByText('Toggle')).toBeInTheDocument()   })    it('should render light as default theme', () => {     render(<App />)     expect(screen.getByText('current theme: light')).toBeInTheDocument()   })    it('should render dark theme on toggle', async () => {     const user = userEvent.setup()     render(<App />)      await user.click(screen.getByText('Toggle'))      expect(screen.getByText('current theme: dark')).toBeInTheDocument()   }) }) 

本攻略注意点:

  • 不需要测试替身,它可以用更少的代码进行测试
  • 测试的行为与用户在真实应用程序中的行为相匹配

本节中使用的完整代码可在 github 上获取

每种方法的优缺点

在本节中,我们将讨论每种方法在不同属性方面的优缺点。

重构 props

在上下文中使用测试替身会使测试对于这种更改变得脆弱。使用 props 重构 usecontext 的使用会自动使测试失败,即使行为没有失败。使用不使用测试双精度的选项支持在这个意义上的重构。

创建自定义上下文

使用自定义上下文而不是直接依赖reactjs 的上下文提供程序也会发生同样的情况。使用不带测试替身的选项可以进行重构。

结论

在本指南中,我们探索了如何测试依赖于上下文的组件,而不需要测试替身,使测试更加简单,更接近真实的用户交互,并对比每种方法的优缺点。只要有可能,应遵循反映用户交互的简单方法。然而,当需要测试替身时,应该以测试代码的可维护性为目标来使用它们。通过简单的测试可以放心地重构生产代码。

资源

  • 创建自定义上下文
  • 重构目录
  • 用于查找如何使用 vitest 模拟模块的特定部分
  • 用于查找如何解决类型问题
  • 测试库 userevent

下一步

  • 尝试测试涉及多个上下文或嵌套提供程序的更复杂的场景。
  • 虽然我们在本指南中避免了模拟,但在某些情况下模拟是必要的。探索这些场景的高级模拟技术。

通过遵循这些步骤,您可以继续提高测试技能并确保您的 react 应用程序可以重构。

相关阅读