2024年用Jest来给React完成一次妙不可言的~单元测试,2024年最新好文推荐

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

1.如何创建测试快照

快照,顾名思义,允许我们保存给定组件的快照。当您进行更新或重构,并希望获取或比较更改时,它会提供很多帮助。

现在,让我们看一下 App.js 文件的快照。

App.test.js
import React from 'react'
import {render, cleanup} from '@testing-library/react'
import App from '../App'


 afterEach(cleanup)


 it('should take a snapshot', () => {
    const { asFragment } = render(<App />)


    expect(asFragment()).toMatchSnapshot()
})



要获取快照,我们首先必须导入 render 和 cleanup 。这两种方法将在本文中大量使用。

render,顾名思义,有助于渲染React组件。cleanup 作为一个参数传递给 afterEach ,以便在每次测试后清理所有东西,以避免内存泄漏。

接下来,我们可以使用 render 呈现App组件,并从方法中获取 asFragment 作为返回值。最后,确保App组件的片段与快照匹配。

现在,要运行测试,打开您的终端并导航到项目的根目录,并运行以下命令:

npm test

因此,它将创建一个新的文件夹 __snapshots__ 和一个文件 App.test.js:

App.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should take a snapshot 1`] = `
<DocumentFragment>
  <div
    class="App"
  >
    <h1>
      Testing Updated
    </h1>
  </div>
</DocumentFragment>
`;

如果,你在 App.js 中做出更改,测试将失败,因为快照将不再匹配。更新快照可以按 u ,或者将对应快照文件删除即可。

2.测试DOM元素

要测试DOM元素,首先必须查看TestElements.js文件。

TestElements.js
import React from 'react'


const TestElements = () => {
 const [counter, setCounter] = React.useState(0)


 return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={() => setCounter(counter + 1)}> Up</button>
    <button disabled data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
 </>
    )
  }


  export default TestElements

在这里,您唯一需要保留的是 data-testid 。它将用于从测试文件中选择这些元素。现在,让我们完成单元测试:

测试计数器是否为0,以及按钮的禁用状态:

TestElements.test.js
import React from 'react';
import "@testing-library/jest-dom/extend-expect";
import { render, cleanup } from '@testing-library/react';
import TestElements from '../components/TestElements'


afterEach(cleanup);


  it('should equal to 0', () => {
    const { getByTestId } = render(<TestElements />); 
    expect(getByTestId('counter')).toHaveTextContent(0)
   });


   it('should be enabled', () => {
    const { getByTestId } = render(<TestElements />);
    expect(getByTestId('button-up')).not.toHaveAttribute('disabled')
  });


  it('should be disabled', () => {
    const { getByTestId } = render(<TestElements />); 
    expect(getByTestId('button-down')).toBeDisabled()
  });

正如您所看到的,语法与前面的测试非常相似。唯一的区别是,我们使用 getByTestId 选择必要的元素(根据 data-testid )并检查是否通过了测试。换句话说,我们检查 <h1 data-testid="counter">{ counter }</h1> 中的文本内容是否等于0。

这里,像往常一样,我们使用 getByTestId 选择元素和检查第一个测试如果按钮禁用属性。对于第二个,如果按钮是否被禁用。

如果您保存文件或在终端纱线测试中再次运行,测试将通过。

3.测试事件

在编写单元测试之前,让我们首先看下 TestEvents.js 是什么样子的。

import React from 'react'


const TestEvents = () => {
  const [counter, setCounter] = React.useState(0)


return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={() => setCounter(counter + 1)}> Up</button>
    <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
 </>
    )
  }


  export default TestEvents

现在,让我们编写测试。

当我们点击按钮时,测试计数器的增减是否正确:

import React from 'react';
import "@testing-library/jest-dom/extend-expect";
import { render, cleanup, fireEvent } from '@testing-library/react';
import TestEvents from '../components/TestEvents'


  afterEach(cleanup);


  it('increments counter', () => {
    const { getByTestId } = render(<TestEvents />); 


    fireEvent.click(getByTestId('button-up'))


    expect(getByTestId('counter')).toHaveTextContent('1')
  });


  it('decrements counter', () => {
    const { getByTestId } = render(<TestEvents />); 


    fireEvent.click(getByTestId('button-down'))


    expect(getByTestId('counter')).toHaveTextContent('-1')
  });

可以看到,除了预期的文本内容之外,这两个测试非常相似。

第一个测试使用 fireEvent.click() 触发一个 click 事件,检查单击按钮时计数器是否增加到1。

第二个检查当点击按钮时计数器是否减为-1。

fireEvent 有几个可以用来测试事件的方法,因此您可以自由地深入文档了解更多信息。

现在我们已经知道了如何测试事件,接下来我们将在下一节中学习如何处理异步操作。

4. 测试异步操作

异步操作是需要时间才能完成的操作。它可以是HTTP请求、计时器等等。

现在,让我们检查 TestAsync.js 文件。

import React from 'react'


const TestAsync = () => {
  const [counter, setCounter] = React.useState(0)


  const delayCount = () => (
    setTimeout(() => {
      setCounter(counter + 1)
    }, 500)
  )


return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={delayCount}> Up</button>
    <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
 </>
    )
  }


  export default TestAsync

这里,我们使用 setTimeout() 将递增事件延迟0.5秒。

测试计数器在0.5秒后判断是否增加:

TestAsync.test.js
import React from 'react';
import "@testing-library/jest-dom/extend-expect";
import { render, cleanup, fireEvent, waitForElement } from '@testing-library/react';
import TestAsync from '../components/TestAsync'


afterEach(cleanup);


  it('increments counter after 0.5s', async () => {
    const { getByTestId, getByText } = render(<TestAsync />); 


    fireEvent.click(getByTestId('button-up'))


    const counter = await waitForElement(() => getByText('1')) 


    expect(counter).toHaveTextContent('1')


});

要测试递增事件,我们首先必须使用 async/await 来处理操作,因为如前所述,完成它需要时间。

接下来,我们使用一个新的助手方法 getByText()。这类似于getByTestId()getByText()选择文本内容,而不是id。

现在,在单击按钮之后,我们等待 waitForElement(() => getByText('1') 来增加计数器。一旦计数器增加到1,我们现在可以移动到条件并检查计数器是否等于1。

也就是说,现在让我们转向更复杂的测试用例。

你准备好了吗?

5.测试 React Redux

让我们检查一下 TestRedux.js 是什么样子的。

TestRedux.js
import React from 'react'
import { connect } from 'react-redux'


const TestRedux = ({counter, dispatch}) => {


 const increment = () => dispatch({ type: 'INCREMENT' })
 const decrement = () => dispatch({ type: 'DECREMENT' })


 return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={increment}>Up</button>
    <button data-testid="button-down" onClick={decrement}>Down</button>
 </>
    )
  }


export default connect(state => ({ counter: state.count }))(TestRedux)

store/reducer.js
export const initialState = {
    count: 0,
  }


  export function reducer(state = initialState, action) {
    switch (action.type) {
      case 'INCREMENT':
        return {
          count: state.count + 1,
        }
      case 'DECREMENT':
        return {
          count: state.count - 1,
        }
      default:
        return state
    }
  }

正如您所看到的,没有什么特别的。它只是一个由 React Redux 处理的基本计数器组件。

现在,让我们来编写单元测试。

测试初始状态是否为0:

import React from 'react'
import "@testing-library/jest-dom/extend-expect";
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { render, cleanup, fireEvent } from '@testing-library/react';
import { initialState, reducer } from '../store/reducer'
import TestRedux from '../components/TestRedux'


const renderWithRedux = (
  component,
  { initialState, store = createStore(reducer, initialState) } = {}
) => {
  return {
    ...render(<Provider store={store}>{component}</Provider>),
    store,
  }
}


 afterEach(cleanup);


it('checks initial state is equal to 0', () => {
    const { getByTestId } = renderWithRedux(<TestRedux />)
    expect(getByTestId('counter')).toHaveTextContent('0')
  })


  it('increments the counter through redux', () => {
    const { getByTestId } = renderWithRedux(<TestRedux />, 
      {initialState: {count: 5}
  })
    fireEvent.click(getByTestId('button-up'))
    expect(getByTestId('counter')).toHaveTextContent('6')
  })


  it('decrements the counter through redux', () => {
    const { getByTestId} = renderWithRedux(<TestRedux />, {
      initialState: { count: 100 },
    })
    fireEvent.click(getByTestId('button-down'))
    expect(getByTestId('counter')).toHaveTextContent('99')
  })

我们需要导入一些东西来测试 React Redux 。这里,我们创建了自己的助手函数 renderWithRedux() 来呈现组件,因为它将被多次使用。

renderWithRedux() 作为参数接收要呈现的组件、初始状态和存储。如果没有存储,它将创建一个新的存储,如果它没有接收初始状态或存储,它将返回一个空对象。

接下来,我们使用render()来呈现组件并将存储传递给提供者。

也就是说,我们现在可以将组件 TestRedux 传递给 renderWithRedux() 来测试计数器是否等于0。

测试计数器的增减是否正确:

为了测试递增和递减事件,我们将初始状态作为第二个参数传递给renderWithRedux()。现在,我们可以单击按钮并测试预期的结果是否符合条件。

现在,让我们进入下一节并介绍 React Context。

6. 测试 React Context

让我们检查一下 TextContext.js 是什么样子的。

import React from "react"


export const CounterContext = React.createContext()


const CounterProvider = () => {
  const [counter, setCounter] = React.useState(0)
  const increment = () => setCounter(counter + 1)
  const decrement = () => setCounter(counter - 1)


  return (
    <CounterContext.Provider value={{ counter, increment, decrement }}>
      <Counter />
    </CounterContext.Provider>
  )
}


export const Counter = () => {  
    const { counter, increment, decrement } = React.useContext(CounterContext)   
    return (
     <>
       <h1 data-testid="counter">{ counter }</h1>
       <button data-testid="button-up" onClick={increment}> Up</button>
       <button data-testid="button-down" onClick={decrement}>Down</button>
    </>
       )
}


export default CounterProvider

现在,通过 React Context 管理计数器状态。让我们编写单元测试来检查它是否按预期运行。

测试初始状态是否为0:

TextContext.test.js
import React from 'react'
import "@testing-library/jest-dom/extend-expect";
import { render, cleanup,  fireEvent } from '@testing-library/react'
import CounterProvider, { CounterContext, Counter } from '../components/TestContext'


const renderWithContext = (
  component) => {
  return {
    ...render(
        <CounterProvider value={CounterContext}>
            {component}
        </CounterProvider>)
  }
}


afterEach(cleanup);


it('checks if initial state is equal to 0', () => {
    const { getByTestId } = renderWithContext(<Counter />)
    expect(getByTestId('counter')).toHaveTextContent('0')
})


it('increments the counter', () => {
    const { getByTestId } = renderWithContext(<Counter />)


    fireEvent.click(getByTestId('button-up'))
    expect(getByTestId('counter')).toHaveTextContent('1')
  })


  it('decrements the counter', () => {
    const { getByTestId} = renderWithContext(<Counter />)


    fireEvent.click(getByTestId('button-down'))
    expect(getByTestId('counter')).toHaveTextContent('-1')
  })

与前面的React Redux部分一样,这里我们使用相同的方法,创建一个助手函数renderWithContext()来呈现组件。但是这一次,它只接收作为参数的组件。为了创建新的上下文,我们将CounterContext传递给 Provider。

现在,我们可以测试计数器最初是否等于0。那么,计数器的增减是否正确呢?

正如您所看到的,这里我们触发一个 click 事件来测试计数器是否正确地增加到1并减少到-1。

也就是说,我们现在可以进入下一节并介绍React Router。

7. 测试 React Router

让我们检查一下 TestRouter.js 是什么样子的。

TestRouter.js
import React from 'react'


import { Link, Route, Switch,  useParams } from 'react-router-dom'


const About = () => <h1>About page</h1>


const Home = () => <h1>Home page</h1>


const Contact = () => {
 const { name } = useParams()


 return <h1 data-testid="contact-name">{name}</h1>
}


const TestRouter = () => {
    const name = 'John Doe'
    return (
    <>
    <nav data-testid="navbar">
      <Link data-testid="home-link" to="/">Home</Link>
      <Link data-testid="about-link" to="/about">About</Link>
      <Link data-testid="contact-link" to={`/contact/${name}`}>Contact</Link>
    </nav>


      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/about:name" component={Contact} />
      </Switch>
    </>
  )
}


export default TestRouter

这里,将测试路由对应的页面信息是否正确。

TestRouter.test.js
import React from 'react'
import "@testing-library/jest-dom/extend-expect";
import { Router } from 'react-router-dom'
import { render, fireEvent } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import TestRouter from '../components/TestRouter'




const renderWithRouter = (component) => {
    const history = createMemoryHistory()
    return { 
    ...render (
    <Router history={history}>
        {component}
    </Router>
    )
  }
}


it('should render the home page', () => {


  const { container, getByTestId } = renderWithRouter(<TestRouter />) 
  const navbar = getByTestId('navbar')
  const link = getByTestId('home-link')


  expect(container.innerHTML).toMatch('Home page')
  expect(navbar).toContainElement(link)
})


it('should navigate to the about page', ()=> {
    const { container, getByTestId } = renderWithRouter(<TestRouter />) 


    fireEvent.click(getByTestId('about-link'))


    expect(container.innerHTML).toMatch('About page')
  })


  it('should navigate to the contact page with the params', ()=> {
    const { container, getByTestId } = renderWithRouter(<TestRouter />) 


    fireEvent.click(getByTestId('contact-link'))


    expect(container.innerHTML).toMatch('John Doe')
  })

要测试React Router,我们首先必须有一个导航历史记录。因此,我们使用 createMemoryHistory() 来创建导航历史。

接下来,我们使用助手函数 renderWithRouter() 来呈现组件,并将历史记录传递给路由器组件。这样,我们现在就可以测试在开始时加载的页面是否是主页。以及导航栏是否加载了预期的链接。

测试当我们点击链接时,它是否用参数导航到其他页面:

现在,要检查导航是否工作,我们必须触发导航链接上的单击事件。

对于第一个测试,我们检查内容是否等于About页面中的文本,对于第二个测试,我们测试路由参数并检查它是否正确通过。

现在我们可以进入最后一节,学习如何测试Axios请求。

8. 测试HTTP请求

让我们检查一下 TestRouter.js 是什么样子的。

import React from 'react'
import axios from 'axios'


const TestAxios = ({ url }) => {
  const [data, setData] = React.useState()


  const fetchData = async () => {
    const response = await axios.get(url)
    setData(response.data.greeting)    
 }     


 return (
  <>
    <button onClick={fetchData} data-testid="fetch-data">Load Data</button>
    { 
    data ?
    <div data-testid="show-data">{data}</div>:
    <h1 data-testid="loading">Loading...</h1>
    }
  </>
     )


}


export default TestAxios

正如您在这里看到的,我们有一个简单的组件,它有一个用于发出请求的按钮。如果数据不可用,它将显示一个加载消息。

现在,让我们编写测试。

来验证数据是否正确获取和显示:

TextAxios.test.js
import React from 'react'
import "@testing-library/jest-dom/extend-expect";
import { render, waitForElement, fireEvent } from '@testing-library/react'
import axiosMock from 'axios'
import TestAxios from '../components/TestAxios'


jest.mock('axios')



![img](https://img-blog.csdnimg.cn/img_convert/ac42dd2b1eb39d3960f65d4718c8d62d.png)
![img](https://img-blog.csdnimg.cn/img_convert/1e05c7ce21fe586d115e7df3eea80ace.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

/h1>
    }
  </>
     )


}


export default TestAxios

正如您在这里看到的,我们有一个简单的组件,它有一个用于发出请求的按钮。如果数据不可用,它将显示一个加载消息。

现在,让我们编写测试。

来验证数据是否正确获取和显示:

TextAxios.test.js
import React from 'react'
import "@testing-library/jest-dom/extend-expect";
import { render, waitForElement, fireEvent } from '@testing-library/react'
import axiosMock from 'axios'
import TestAxios from '../components/TestAxios'


jest.mock('axios')



[外链图片转存中...(img-smttNare-1715260266263)]
[外链图片转存中...(img-T4qn0TSb-1715260266264)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值