溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么使用React Testing Library和Jest完成單元測試

發(fā)布時間:2021-11-05 11:40:45 來源:億速云 閱讀:166 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“怎么使用React Testing Library和Jest完成單元測試”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么使用React Testing Library和Jest完成單元測試”吧!

技術(shù)棧選擇

當(dāng)我們想要為 React 應(yīng)用編寫單元測試的時候,官方推薦是使用 React Testing Library + Jest 的方式。Enzyme 也是十分出色的單元測試庫,我們應(yīng)該選擇哪種測試工具呢?

下面讓我們看一個簡單的計數(shù)器的例子,以及兩個相應(yīng)的測試:第一個是使用 Enzyme 編寫的,第二個是使用 React Testing Library 編寫的。

  • counter.js 

// counter.js  import React from "react";  class Counter extends React.Component {    state = { count: 0 };    increment = () => this.setState(({ count }) => ({ count: count + 1 }));    decrement = () => this.setState(({ count }) => ({ count: count - 1 }));    render() {      return (        <div>          <button onClick={this.decrement}>-</button>          <p>{this.state.count}</p>          <button onClick={this.increment}>+</button>        </div>      );    }  }  export default Counter;
  • counter-enzyme.test.js 

// counter-enzyme.test.js  import React from "react";  import { shallow } from "enzyme";  import Counter from "./counter";  describe("<Counter />", () => {    it("properly increments and decrements the counter", () => {      const wrapper = shallow(<Counter />);      expect(wrapper.state("count")).toBe(0);      wrapper.instance().increment();      expect(wrapper.state("count")).toBe(1);      wrapper.instance().decrement();      expect(wrapper.state("count")).toBe(0);    });  });
  • counter-rtl.test.js 

// counter-rtl.test.js  import React from "react";  import { render, fireEvent } from "@testing-library/react";  import Counter from "./counter";  describe("<Counter />", () => {    it("properly increments and decrements the counter", () => {      const { getByText } = render(<Counter />);      const counter = getByText("0");      const incrementButton = getByText("+");      const decrementButton = getByText("-");      fireEvent.click(incrementButton);      expect(counter.textContent).toEqual("1");      fireEvent.click(decrementButton);      expect(counter.textContent).toEqual("0");    });  });

比較兩個例子,你能看出哪個測試文件是最好的嘛?如果你不是很熟悉單元測試,可能會任務(wù)兩種都很好。但是實際上 Enzyme 的實現(xiàn)有兩個誤報的風(fēng)險:

  •     即使代碼損壞,測試也會通過。

  •     即使代碼正確,測試也會失敗。

讓我們來舉例說明這兩點。假設(shè)您希望重構(gòu)組件,因為您希望能夠設(shè)置任何count值。因此,您可以刪除遞增和遞減方法,然后添加一個新的setCount方法。假設(shè)你忘記將這個新方法連接到不同的按鈕:

  • counter.js 

// counter.js  export default class Counter extends React.Component {    state = { count: 0 };    setCount = count => this.setState({ count });    render() {      return (        <div>          <button onClick={this.decrement}>-</button>          <p>{this.state.count}</p>          <button onClick={this.increment}>+</button>        </div>      );    }  }

第一個測試(Enzyme)將通過,但第二個測試(RTL)將失敗。實際上,第一個并不關(guān)心按鈕是否正確地連接到方法。它只查看實現(xiàn)本身,也就是說,您的遞增和遞減方法執(zhí)行之后,應(yīng)用的狀態(tài)是否正確。

這就是代碼損壞,測試也會通過。

現(xiàn)在是2020年,你也許聽說過 React Hooks,并且打算使用 React Hooks 來改寫我們的計數(shù)器代碼:

  • counter.js 

// counter.js  import React, { useState } from "react";  export default function Counter() {    const [count, setCount] = useState(0);    const increment = () => setCount(count => count + 1);    const decrement = () => setCount(count => count - 1);    return (      <div>        <button onClick={decrement}>-</button>        <p>{count}</p>        <button onClick={increment}>+</button>      </div>    );  }

這一次,即使您的計數(shù)器仍然工作,第一個測試也將被打破。Enzyme 會報錯,函數(shù)組件中無法使用state:

ShallowWrapper::state() can only be called on class components

接下來,就需要改寫單元測試文件了:

  • counter-enzyme.test.js 

import React from "react";  import { shallow } from "enzyme";  import Counter from "./counter";  describe("<Counter />", () => {    it("properly increments and decrements the counter", () => {      const setValue = jest.fn();      const useStateSpy = jest.spyOn(React, "useState");      useStateSpy.mockImplementation(initialValue => [initialValue, setValue]);      const wrapper = shallow(<Counter />);      wrapper        .find("button")        .last()        .props()        .onClick();      expect(setValue).toHaveBeenCalledWith(1);      // We can't make any assumptions here on the real count displayed      // In fact, the setCount setter is mocked!      wrapper        .find("button")        .first()        .props()        .onClick();      expect(setValue).toHaveBeenCalledWith(-1);    });  });

而使用 React Testing Library 編寫的單元測試還是可以正常運行的,因為它更加關(guān)注應(yīng)用的事件處理,以及展示;而非應(yīng)用的實現(xiàn)細節(jié),以及狀態(tài)變化。更加符合我們對于單元測試的原本訴求,以及最佳實踐。

可遵循的簡單規(guī)則

也許上文中使用 React Testing Library 編寫的單元測試示例,還會給人一種一頭霧水的感覺。下面,讓我們使用 AAA 模式來一步一步的拆解這部分代碼。

AAA模式:編排(Arrange),執(zhí)行(Act),斷言(Assert)。

幾乎所有的測試都是這樣寫的。首先,您要編排(初始化)您的代碼,以便為接下來的步驟做好一切準(zhǔn)備。然后,您執(zhí)行用戶應(yīng)該執(zhí)行的步驟(例如單擊)。最后,您對應(yīng)該發(fā)生的事情進行斷言。

import React from "react";  import { render, fireEvent } from "@testing-library/react";  import Counter from "./app";  describe("<Counter />", () => {    it("properly increments the counter", () => {      // Arrange      const { getByText } = render(<Counter />);      const counter = getByText("0");      const incrementButton = getByText("+");      const decrementButton = getByText("-");      // Act      fireEvent.click(incrementButton);      // Assert      expect(counter.textContent).toEqual("1");      // Act      fireEvent.click(decrementButton);      // Assert     expect(counter.textContent).toEqual("0");    });  });

編排(Arrange)

在編排這一步,我們需要完成2項任務(wù):

  •  渲染組件

  •  獲取所需的DOM的不同元素。

渲染組件可以使用 RTL's API 的 render 方法完成。簽名如下:

function render(    ui: React.ReactElement,    options?: Omit<RenderOptions, 'queries'>  ): RenderResult

ui 是你要加載的組件。options 通常不需要指定選項。官方文檔在這里,如果要指定的話,如下值是對官方文檔的簡單摘錄:

  •  container:React Testing庫將創(chuàng)建一個div并將該div附加到文檔中。而通過這個參數(shù),可以自定義容器。

  •  baseElement:

如果指定了容器,則此值默認為該值,否則此值默認為document.documentElement。這將用作查詢的基本元素,以及在使用debug()時打印的內(nèi)容。

  •  hydrate:用于服務(wù)端渲染,使用 ReactDOM.hydrate 加載你的組件。

  •  wrapper:傳遞一個組件作為包裹層,將我們要測試的組件渲染在其中。這通常用于創(chuàng)建可以重用的自定義 render 函數(shù),以便提供常用數(shù)據(jù)。

  •  queries:查詢綁定。除非合并,否則將覆蓋DOM測試庫中的默認設(shè)置。

基本上,這個函數(shù)所做的就是使用ReactDOM呈現(xiàn)組件。在直接附加到document.body的新創(chuàng)建的div中呈現(xiàn)(或為服務(wù)器端呈現(xiàn)提供水合物)。因此,可以從DOM測試庫和其他一些有用的方法(如debug、rerender或unmount)獲得大量查詢。

文檔:https://testing-library.com/d...

但你可能會想,這些問題是什么呢?有些實用程序允許您像用戶那樣查詢DOM:通過標(biāo)簽文本、占位符和標(biāo)題查找元素。以下是一些來自文檔的查詢示例:

  •  getByLabelText:搜索與作為參數(shù)傳遞的給定文本匹配的標(biāo)簽,然后查找與該標(biāo)簽關(guān)聯(lián)的元素。

  •  getByText:搜索具有文本節(jié)點的所有元素,其中的textContent與作為參數(shù)傳遞的給定文本匹配。

  •  getByTitle:返回具有與作為參數(shù)傳遞的給定文本匹配的title屬性的元素。

  •  getByPlaceholderText:搜索具有占位符屬性的所有元素,并找到與作為參數(shù)傳遞的給定文本相匹配的元素。

一個特定的查詢有很多變體:

  •  getBy:返回查詢的第一個匹配節(jié)點,如果沒有匹配的元素或找到多個匹配,則拋出一個錯誤。

  •  getAllBy:返回一個查詢中所有匹配節(jié)點的數(shù)組,如果沒有匹配的元素,則拋出一個錯誤。

  •  queryBy:返回查詢的第一個匹配節(jié)點,如果沒有匹配的元素,則返回null。這對于斷言不存在的元素非常有用。

  •  queryAllBy:返回一個查詢的所有匹配節(jié)點的數(shù)組,如果沒有匹配的元素,則返回一個空數(shù)組([])。

  •  findBy:返回一個promise,該promise將在找到與給定查詢匹配的元素時解析。如果未找到任何元素,或者在默認超時時間為4500毫秒后找到了多個元素,則承諾將被拒絕。

  •  findAllBy:返回一個promise,當(dāng)找到與給定查詢匹配的任何元素時,該promise將解析為元素數(shù)組。

  • 執(zhí)行(Act)

現(xiàn)在一切都準(zhǔn)備好了,我們可以行動了。為此,我們大部分時間使用了來自DOM測試庫的fireEvent,其簽名如下:

fireEvent(node: HTMLElement, event: Event)

簡單地說,這個函數(shù)接受一個DOM節(jié)點(您可以使用上面看到的查詢查詢它!)并觸發(fā)DOM事件,如單擊、焦點、更改等。您可以在這里找到許多其他可以調(diào)度的事件。

我們的例子相當(dāng)簡單,因為我們只是想點擊一個按鈕,所以我們只需:

fireEvent.click(incrementButton);  // OR  fireEvent.click(decrementButton);

斷言(Assert)

接下來是最后一部分。觸發(fā)事件通常會觸發(fā)應(yīng)用程序中的一些更改,因此我們必須執(zhí)行一些斷言來確保這些更改發(fā)生。在我們的測試中,這樣做的一個好方法是確保呈現(xiàn)給用戶的計數(shù)已經(jīng)更改。因此,我們只需斷言textContent屬性的計數(shù)器是遞增或遞減:

expect(counter.textContent).toEqual("1");  expect(counter.textContent).toEqual("0");

恭喜你,到這里你已經(jīng)將我們的示例拆解成功。 ?

注意:這個AAA模式并不特定于測試庫。事實上,它甚至是任何測試用例的一般結(jié)構(gòu)。我在這里向您展示這個是因為我發(fā)現(xiàn)測試庫如何方便地在每個部分中編寫測試是一件很有趣的事情。

8個典型的例子

到這里,就進入實戰(zhàn)階段了,接下來請先下載示例:rts-guide-demo 。

安裝依賴的同時可以簡單看下我們的項目。src/test 目錄下存放了所有單元測試相關(guān)的文件。讓我們清空這個文件夾,再將下面的示例依次手過一遍。?(CV也是可以的?)

1.如何創(chuàng)建測試快照

快照,顧名思義,允許我們保存給定組件的快照。當(dāng)您進行更新或重構(gòu),并希望獲取或比較更改時,它會提供很多幫助。

現(xiàn)在,讓我們看一下 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()  })

要獲取快照,我們首先必須導(dǎo)入 render 和 cleanup 。這兩種方法將在本文中大量使用。

render,顧名思義,有助于渲染React組件。cleanup 作為一個參數(shù)傳遞給 afterEach ,以便在每次測試后清理所有東西,以避免內(nèi)存泄漏。

接下來,我們可以使用 render 呈現(xiàn)App組件,并從方法中獲取 asFragment 作為返回值。最后,確保App組件的片段與快照匹配。

現(xiàn)在,要運行測試,打開您的終端并導(dǎo)航到項目的根目錄,并運行以下命令:

npm test

因此,它將創(chuàng)建一個新的文件夾 __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"    >      <h2>        Testing Updated      </h2>    </div>  </DocumentFragment>  `;

如果,你在 App.js 中做出更改,測試將失敗,因為快照將不再匹配。更新快照可以按 u ,或者將對應(yīng)快照文件刪除即可。

2.測試DOM元素

要測試DOM元素,首先必須查看TestElements.js文件。

  • TestElements.js 

import React from 'react'  const TestElements = () => {   const [counter, setCounter] = React.useState(0)   return (    <>      <h2 data-testid="counter">{ counter }</h2>      <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 。它將用于從測試文件中選擇這些元素?,F(xiàn)在,讓我們完成單元測試:

測試計數(shù)器是否為0,以及按鈕的禁用狀態(tài):

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

正如您所看到的,語法與前面的測試非常相似。唯一的區(qū)別是,我們使用 getByTestId 選擇必要的元素(根據(jù) data-testid )并檢查是否通過了測試。換句話說,我們檢查 <h2 data-testid="counter">{ counter }</h2> 中的文本內(nèi)容是否等于0。

這里,像往常一樣,我們使用 getByTestId 選擇元素和檢查第一個測試如果按鈕禁用屬性。對于第二個,如果按鈕是否被禁用。

如果您保存文件或在終端紗線測試中再次運行,測試將通過。

3.測試事件

在編寫單元測試之前,讓我們首先看下 TestEvents.js 是什么樣子的。

import React from 'react'  const TestEvents = () => {    const [counter, setCounter] = React.useState(0)  return (    <>      <h2 data-testid="counter">{ counter }</h2>      <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

現(xiàn)在,讓我們編寫測試。

當(dāng)我們點擊按鈕時,測試計數(shù)器的增減是否正確:

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')    });

可以看到,除了預(yù)期的文本內(nèi)容之外,這兩個測試非常相似。

第一個測試使用 fireEvent.click() 觸發(fā)一個 click 事件,檢查單擊按鈕時計數(shù)器是否增加到1。

第二個檢查當(dāng)點擊按鈕時計數(shù)器是否減為-1。

fireEvent 有幾個可以用來測試事件的方法,因此您可以自由地深入文檔了解更多信息。

現(xiàn)在我們已經(jīng)知道了如何測試事件,接下來我們將在下一節(jié)中學(xué)習(xí)如何處理異步操作。

4. 測試異步操作

異步操作是需要時間才能完成的操作。它可以是HTTP請求、計時器等等。

現(xiàn)在,讓我們檢查 TestAsync.js 文件。

import React from 'react' const TestAsync = () => {    const [counter, setCounter] = React.useState(0)    const delayCount = () => (      setTimeout(() => {        setCounter(counter + 1)      }, 500)    )  return (    <>      <h2 data-testid="counter">{ counter }</h2>      <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秒。

測試計數(shù)器在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()選擇文本內(nèi)容,而不是id。

現(xiàn)在,在單擊按鈕之后,我們等待 waitForElement(() => getByText('1') 來增加計數(shù)器。一旦計數(shù)器增加到1,我們現(xiàn)在可以移動到條件并檢查計數(shù)器是否等于1。

也就是說,現(xiàn)在讓我們轉(zhuǎn)向更復(fù)雜的測試用例。

你準(zhǔn)備好了嗎?

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 (    <>      <h2 data-testid="counter">{ counter }</h2>      <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 處理的基本計數(shù)器組件。

現(xiàn)在,讓我們來編寫單元測試。

測試初始狀態(tài)是否為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')    })

我們需要導(dǎo)入一些東西來測試 React Redux 。這里,我們創(chuàng)建了自己的助手函數(shù) renderWithRedux() 來呈現(xiàn)組件,因為它將被多次使用。

renderWithRedux() 作為參數(shù)接收要呈現(xiàn)的組件、初始狀態(tài)和存儲。如果沒有存儲,它將創(chuàng)建一個新的存儲,如果它沒有接收初始狀態(tài)或存儲,它將返回一個空對象。

接下來,我們使用render()來呈現(xiàn)組件并將存儲傳遞給提供者。

也就是說,我們現(xiàn)在可以將組件 TestRedux 傳遞給 renderWithRedux() 來測試計數(shù)器是否等于0。

測試計數(shù)器的增減是否正確:

為了測試遞增和遞減事件,我們將初始狀態(tài)作為第二個參數(shù)傳遞給renderWithRedux()?,F(xiàn)在,我們可以單擊按鈕并測試預(yù)期的結(jié)果是否符合條件。

現(xiàn)在,讓我們進入下一節(jié)并介紹 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 (       <>         <h2 data-testid="counter">{ counter }</h2>         <button data-testid="button-up" onClick={increment}> Up</button>         <button data-testid="button-down" onClick={decrement}>Down</button>      </>         )  }  export default CounterProvider

現(xiàn)在,通過 React Context 管理計數(shù)器狀態(tài)。讓我們編寫單元測試來檢查它是否按預(yù)期運行。

測試初始狀態(tài)是否為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部分一樣,這里我們使用相同的方法,創(chuàng)建一個助手函數(shù)renderWithContext()來呈現(xiàn)組件。但是這一次,它只接收作為參數(shù)的組件。為了創(chuàng)建新的上下文,我們將CounterContext傳遞給 Provider。

現(xiàn)在,我們可以測試計數(shù)器最初是否等于0。

那么,計數(shù)器的增減是否正確呢?

正如您所看到的,這里我們觸發(fā)一個 click 事件來測試計數(shù)器是否正確地增加到1并減少到-1。

也就是說,我們現(xiàn)在可以進入下一節(jié)并介紹React Router。

7. 測試 React Router

讓我們檢查一下 TestRouter.js 是什么樣子的。

  • TestRouter.js 

import React from 'react'  import { Link, Route, Switch,  useParams } from 'react-router-dom'  const About = () => <h2>About page</h2>  const Home = () => <h2>Home page</h2>  const Contact = () => {   const { name } = useParams()   return <h2 data-testid="contact-name">{name}</h2>  }  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

這里,將測試路由對應(yīng)的頁面信息是否正確。

  • 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,我們首先必須有一個導(dǎo)航歷史記錄。因此,我們使用 createMemoryHistory() 來創(chuàng)建導(dǎo)航歷史。

接下來,我們使用助手函數(shù) renderWithRouter() 來呈現(xiàn)組件,并將歷史記錄傳遞給路由器組件。這樣,我們現(xiàn)在就可以測試在開始時加載的頁面是否是主頁。以及導(dǎo)航欄是否加載了預(yù)期的鏈接。

測試當(dāng)我們點擊鏈接時,它是否用參數(shù)導(dǎo)航到其他頁面:

現(xiàn)在,要檢查導(dǎo)航是否工作,我們必須觸發(fā)導(dǎo)航鏈接上的單擊事件。

對于第一個測試,我們檢查內(nèi)容是否等于About頁面中的文本,對于第二個測試,我們測試路由參數(shù)并檢查它是否正確通過。

現(xiàn)在我們可以進入最后一節(jié),學(xué)習(xí)如何測試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>:      <h2 data-testid="loading">Loading...</h2>      }    </>       ) } export default TestAxios

正如您在這里看到的,我們有一個簡單的組件,它有一個用于發(fā)出請求的按鈕。如果數(shù)據(jù)不可用,它將顯示一個加載消息。

現(xiàn)在,讓我們編寫測試。

來驗證數(shù)據(jù)是否正確獲取和顯示:

  • 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') it('should display a loading text', () => {   const { getByTestId } = render(<TestAxios />)    expect(getByTestId('loading')).toHaveTextContent('Loading...')  })  it('should load and display the data', async () => {    const url = '/greeting'    const { getByTestId } = render(<TestAxios url={url} />)    axiosMock.get.mockResolvedValueOnce({      data: { greeting: 'hello there' },    })    fireEvent.click(getByTestId('fetch-data'))    const greetingData = await waitForElement(() => getByTestId('show-data'))    expect(axiosMock.get).toHaveBeenCalledTimes(1)    expect(axiosMock.get).toHaveBeenCalledWith(url)    expect(greetingData).toHaveTextContent('hello there')  })

這個測試用例有點不同,因為我們必須處理HTTP請求。為此,我們必須在jest.mock('axios')的幫助下模擬axios請求。

現(xiàn)在,我們可以使用axiosMock并對其應(yīng)用get()方法。最后,我們將使用Jest函數(shù)mockResolvedValueOnce()來傳遞模擬數(shù)據(jù)作為參數(shù)。

現(xiàn)在,對于第二個測試,我們可以單擊按鈕來獲取數(shù)據(jù)并使用async/await來解析它?,F(xiàn)在我們要測試三件事:

  •  如果HTTP請求已經(jīng)正確完成

  •  如果使用url完成了HTTP請求

  •  如果獲取的數(shù)據(jù)符合期望。

對于第一個測試,我們只檢查加載消息在沒有數(shù)據(jù)要顯示時是否顯示。

也就是說,我們現(xiàn)在已經(jīng)完成了八個簡單的步驟來測試你的React應(yīng)用程序。

感謝各位的閱讀,以上就是“怎么使用React Testing Library和Jest完成單元測試”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么使用React Testing Library和Jest完成單元測試這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI