既有适合小白学习的零基础资料,也有适合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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**