react測試用例

什么是測試用例

字面理解來說石景,就是測試一個東西更舞,對于我們前端開發(fā)來說就是測試前端功能畦幢,前端主要分為 html css javascript,一般來說是測試 js疏哗,現(xiàn)在主要通過模塊的形式來開發(fā),對于 react 來說禾怠,html 也屬于 js 的一部分返奉,css 怎么測試,或許你需要判斷渲染的組件吗氏,有沒有className芽偏。

測試用例的作用是什么

舉個例子來說明測試用例的簡單場景

對于剛接觸測試用例的來說,會不會覺得很浪費時間弦讽,我的功能都可以正常運(yùn)行為什么要沒事干去寫測試用例污尉,干點其他的不好嗎膀哲。其實這樣想是正常的,但是到后面開發(fā)某些工具方法或者通用組件的時候就會體現(xiàn)測試用例的重要性了被碗。
例:(環(huán)境搭建后面會提某宪,這里需要先搞定測試用例是什么)

現(xiàn)在有 sum 方法,就是一個簡單的計算锐朴,App 組件兴喂,現(xiàn)在功能正常運(yùn)行,渲染為 3 焚志,預(yù)期很符合我們想要的衣迷。這個測試用例就是用來保證這個函數(shù)的輸入輸出是符合預(yù)期的。

export default function sum(a, b) {
    return a + b;
}
function App() {
  return (
    <div>
      {sum(1, 2)}
    </div>
  );
}
測試用例代碼酱酬,先簡單的看一下大概語法,
// describe 和 test 先不看 
describe('測試sum方法', () => {
    test('測試 1 + 2 = 3', () => {
      // expect 來源于 jest 語法壶谒,expect  就是拿到一個值,這里可以是函數(shù)調(diào)用膳沽,也可以是常量
      // toBe() 是判斷方式的一種方式汗菜,
      // 按照代碼來理解就是 sum(1, 2) === 3
      // jest文檔 https://jestjs.io/docs/en/expect
        expect(sum(1, 2)).toBe(3);
    });
});
測試用例運(yùn)行結(jié)果
image.png
但是到后面功能開發(fā)的時候,可能會使用后臺返回的數(shù)據(jù)來進(jìn)行渲染贵少,如果現(xiàn)在的數(shù)據(jù)是這樣
const testData = {
  a: '1',
  b: '2'
};

function App() {
  return (
    <div>
      {sum(testData.a, testData.b)}
    </div>
  );
}

后臺給的數(shù)據(jù)是字符串呵俏,我們確直接使用了,現(xiàn)在得到 '12' 滔灶,肯定是不符合預(yù)期的普碎,有人說可以用 ts 來,雖然 ts 確實可以避免這個問題录平,但是大家知道代碼量也會上升麻车,這里不考慮 ts 在的情況下。
這個時候大家會想到使用Number等轉(zhuǎn)換為數(shù)字的方法來保證功能的正常運(yùn)行斗这,所以現(xiàn)在我們需要來對 sum 進(jìn)行改造动猬,大家要知道改造就有可能出問題,現(xiàn)在我們來進(jìn)行簡單修改一下

export default function sum(a, b) {
    return Number(a) + Number(b);
}

測試用例也需要同步添加

describe('測試sum方法', () => {

    test('測試 1 + 2 = 3', () => {
        expect(sum(1, 2)).toBe(3);
    });

    test('測試字符串相加', () => {
        expect(sum('1', '2')).toBe(3);
    });

});

現(xiàn)在功能正常運(yùn)行表箭,但是實際上這里還有問題赁咙,如果給的是 '' undefined null 等這里又會有問題
我們這個功能需要兼容各種場景,并不是只能適用于一個需求免钻,現(xiàn)在我們重新改造方法

const parseValue = value => Number.isNaN(Number(value)) ? 0 : Number(value);
export default function sum(a, b) {
    return parseValue(a) + parseValue(b);
}
const testData = {
  a: '1',
  b: undefined
};

function App() {
  return (
    <div>
      {sum(testData.a, testData.b)}
    </div>
  );
}
describe('測試sum方法', () => {

    test('測試 1 + 2 = 3', () => {
        expect(sum(1, 2)).toBe(3);
    });

    test('測試字符串相加', () => {
        expect(sum('1', '2')).toBe(3);
    });

    test('測試空值相加', () => {
        expect(sum(null, undefined)).toBe(0);
        expect(sum('', undefined)).toBe(0);
    });

});
image.png

現(xiàn)在感覺這個看起來可以正常運(yùn)行
但是我們需求總是不同的彼水,比如現(xiàn)在是只支持兩個參數(shù),后面多個參數(shù)也是有可能的极舔,這個時候就需要保證改造的同時支持原來的邏輯不受影響凤覆。到目前為止我們的測試用例仍然是不會出錯的。如果這個方法很復(fù)雜拆魏,別人不小心把 + 改成了 - 或者 * 盯桦,那么這個時候在去執(zhí)行測試用例就能找到問題慈俯,由于新的改動影響到了之前的測試用例,那么說明這次的改動是有問題的拥峦,能及時發(fā)現(xiàn)問題贴膘。

const parseValue = value => Number.isNaN(Number(value)) ? 0 : Number(value);

export default function sum(a, b) {
    return parseValue(a) - parseValue(b);
}
image.png

所以測試用例就是用來保證功能的正常執(zhí)行,如果測試用例寫的場景較少事镣,改成下面這種也是發(fā)現(xiàn)不了問題的步鉴,如果這里傳入 0.1 + 0.2 === 0.3 嗎

describe('測試sum方法', () => {

    test('測試空值相加', () => {
        expect(sum(null, undefined)).toBe(0);
        expect(sum('', undefined)).toBe(0);
    });

});
image.png

如果你的方法寫了足夠全的測試用例,即使別人來改造你這個方法璃哟,也能盡快定位問題并回歸功能氛琢,而且測試用例還能定位到?jīng)]有執(zhí)行的代碼,刪除不用的代碼随闪,建議每次 build 的時候運(yùn)行一次測試用例阳似,避免出現(xiàn)問題(我寫的代碼天下第一,沒有bug铐伴,不需要單測)


image.png

這里提示第6行代碼沒有被執(zhí)行到撮奏,所以需要考慮方法里面的各種分支也是有用的,打包優(yōu)化也是必備的当宴。

測試用例要寫很多

其實是這樣的畜吊,為了避免線上問題,這是很有必要的户矢,因為很多組件或方法帶來的影響不僅僅只有一個地方玲献。對于一些業(yè)務(wù)頁面,通常變化過快梯浪,編寫成本過高捌年,可以多測試頁面的功能,回歸之前的功能(有問題測試背鍋)來代替測試用例的編寫挂洛,如果你的時間足夠充足可以上 ts + 測試用例(那我有時間為啥不弄點其他的)礼预。

現(xiàn)在我們來進(jìn)行react中的測試用例

這里采用 npx create-react-app test-app 來創(chuàng)建一個項目
腳手架已經(jīng)內(nèi)置了 jest 來進(jìn)行測試,如果你說為什么是 jest 虏劲,哪請去問 create-react-app托酸,
如果你是自己用 webpack 來搭建的項目,或者dva柒巫,請參考
jest配置 https://jestjs.io/docs/en/configuration (這或許會比較麻煩励堡,如果是項目初期可以,如果是項目后期需要加測試用例吻育,需要處理各種環(huán)境問題)

我想這是大家都會的(https://www.html.cn/create-react-app/docs/running-tests/

  1. npx create-react-app test-app
  2. cd test-app
  3. npm start
  4. npm install --save enzyme enzyme-adapter-react-16 react-test-renderer
  5. 在 src/setupTests.js 添加以下代碼
    import { configure } from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    configure({ adapter: new Adapter() });

來源于 create-react-app 建議

文件名約定
Jest 將使用以下任何流行的命名約定來查找測試文件:
tests 文件夾中帶有 .js 后綴的文件念秧。
帶有 .test.js 后綴的文件淤井。
帶有 .spec.js 后綴的文件布疼。
.test.js / .spec.js 文件(或 tests 文件夾)可以是位于頂級文件夾 src 下任何深度的文件夾中摊趾。
我們建議將測試文件(或 tests 文件夾)放在他們正在測試的代碼旁邊,以便相對路徑導(dǎo)入時路徑更短游两。 例如砾层,如果 App.test.js 和 App.js 位于同一文件夾中,則測試只需從 import App from './App' 而不是很長的相對路徑贱案。 主機(jī)托管的情況下還有助于在大型項目中更快地找到測試肛炮。

基本庫

  1. 對于斷言庫使用 jest https://jestjs.io/docs/en/configuration ,這個可以用來測試代碼執(zhí)行的結(jié)果宝踪,代碼執(zhí)行了到了什么分支侨糟,什么沒有執(zhí)行到等等。
  2. 對于 react 測試采用 enzyme瘩燥、react-testing-library
    這都是可以用來測試react秕重,只是語法api不同而且,建議了解一下在選擇厉膀,因為測試用例是運(yùn)行在node 環(huán)境下的溶耘,需要一個渲染的環(huán)境,而這個就可以在node環(huán)境里面執(zhí)行你的react代碼服鹅。
  1. src/utils/sum 下新建文件 index.js index.test.js
export default function sum(a, b) {
    return a + b;
}
import sum from './index';

describe('測試sum方法', () => {

    test('測試 1 + 2 = 3', () => {
        expect(sum(1, 2)).toBe(3);
    });

});

  1. 運(yùn)行 npm test凳兵,如果得到以下信息說明目前環(huán)境沒有問題


    image.png

目前目錄結(jié)構(gòu)(文件顏色是因為git的關(guān)系)


image.png

現(xiàn)在來分析測試用例語法 https://jestjs.io/docs/en/getting-started

// describe 這個是 jest 庫方法,創(chuàng)建一個將幾個相關(guān)測試組合在一起的模塊
// test 運(yùn)行測試的方法企软,等同于 it
  test('測試 1 + 2 = 3', () => {
    // expect 返回一個 jest 對象庐扫,可以調(diào)用jest匹配器,
    // 例如:
    // toBe(3) 就是等于 3
    // toBe(true) 就是等于 true
    // toBe(false) 就是等于 false
    // toBeNull 僅匹配 null
    // toBeUndefined 僅匹配 undefined
    // toBeTruthy 匹配if語句視為真實的任何內(nèi)容
    // toBeFalsy 匹配if語句視為假的任何內(nèi)容
    expect(sum(1, 2)).toBe(3);
  });

  // test === it
  it('測試 1 + 2 = 3', () => {
    expect(sum(1, 2)).toBe(3);
  });

是不是很簡單澜倦,就是執(zhí)行前面的代碼邏輯聚蝶,后面來判斷是不是相等,更多api還需要去啃jest文檔藻治。
異步和mock等等

enzyme 對于react我們來新建幾個文件

src/components/Counter index.js index.test.js

import React, { PureComponent } from 'react';

class Index extends PureComponent {

    state = {
        value: 0
    };

    addition = () => {
        this.setState(({ value }) => ({ value: value + 1 }));
    };

    subtraction = () => {
        this.setState(({ value }) => ({ value: value - 1 }));
    };

    render() {
        const { value } = this.state;
        return (
            <div>
                <button onClick={this.addition}>+</button>
                <span>{value}</span>
                <button onClick={this.subtraction}>-</button>
            </div>
        )
    }
}

export default Index;

import React from 'react';
import { shallow } from 'enzyme';
import Counter from './index';

describe('測試 Counter 組件', () => {

    test('測試渲染內(nèi)容', () => {
        // shallow 這個方法返回一個react組件包裝對象碘勉,可以調(diào)用 enzyme 內(nèi)置方法
        // 這是一個簡單的淺渲染,就是說只是渲染當(dāng)前組件的內(nèi)容桩卵,假如組件里面套用了很多其他組件验靡,
        // 就不會被渲染到,import { mount } from 'enzyme'; 這個可以完整渲染雏节,需要根據(jù)自己場景來胜嗓。
        const wrapper = shallow(<Counter />);
         // wrapper.debug() 這個打印渲染組件內(nèi)容,可以用來調(diào)試钩乍。
        console.log(wrapper.debug());
        // wrapper.state() 獲取一個組件的 state辞州,然后在用jest來斷言,就是判斷初始化組件的state.value === 0
        expect(wrapper.state().value).toBe(0);
        // find('div') 方法是選擇器寥粹,類似于 document.querySelector document.querySelectorAll
        // https://enzymejs.github.io/enzyme/docs/api/selector.html
        expect(wrapper.find('div')).not.toBeNull();
        expect(wrapper.find('button').length).toBe(2);
        expect(wrapper.find('span').length).toBe(1);
    });

    test('測試添加', () => {
        const wrapper = shallow(<Counter />);
        const additionButton = wrapper.find('button').at(0);
        /* 
        simulate 這個方法是用來主動觸發(fā)一個元素的事件变过,
        因為我們在測試用例的環(huán)境里面不可能去手動點擊是吧埃元,
        就算可以點擊,也不可能每次跑測試用例來點點點吧媚狰。
        這個操作就是 enzyme 會去執(zhí)行 click 事件岛杀,可以傳入兩個參數(shù)
        第一個參數(shù)是事件類型,第二個是事件參數(shù)崭孤,比如 { target: { value:'1' } }
        然后調(diào)用的方法可以使用這個參數(shù)类嗤,然后我們在重新獲取到最新的state進(jìn)行判斷,
        當(dāng)然了如果你調(diào)用兩次哪判斷條件就要改變辨宠,這里會想遗锣,setState 是異步的,
        為什么這里立馬觸發(fā)了 setState 就可以獲取到最新值呢嗤形,這或許需要深入研究 enzyme 是怎么實現(xiàn)的黄伊。
        */
        additionButton.simulate('click');
        expect(wrapper.state().value).toBe(1);
    });

    test('測試減少', () => {
        const wrapper = shallow(<Counter />);
        const subtractionButton = wrapper.find('button').at(1);
        subtractionButton.simulate('click');
        expect(wrapper.state().value).toBe(-1);
    });

});

運(yùn)行 npm test -- src/components/Counter/index.test.js,注意別忘了 -- 之間的空格
這是npm 命令傳參派殷,jest 可以接收到這個參數(shù)还最,并且只運(yùn)行這一個文件

image.png

現(xiàn)在有一個 計數(shù)器(Counter )組件,一個簡單的內(nèi)部狀態(tài)毡惜,+ - 和展示
但是寫測試用例應(yīng)該從比較底層的組件開始寫拓轻,如果一開始就要以大組件來編寫,可以考慮采用 jest mock 方法來模擬一個簡單的組件经伙。
然后進(jìn)行判斷數(shù)量是不是我們預(yù)期的扶叉,當(dāng)然了也不用逐個判斷,
使用 expect(wrapper.isEmptyRender()).toBeFalsy(); 來判斷是不是返回空也行帕膜,所以有時候即使測試用例
100%覆蓋率也有可能會有問題枣氧,還得看測試用例是怎么去寫的

運(yùn)行 npm test -- --coverage 可以查看覆蓋率,執(zhí)行了那些代碼
并且在package.json添加以下配置垮刹,這代表我們只收集 components 和 utils 下面的文件
https://www.html.cn/create-react-app/docs/running-tests/

"jest": {
    "collectCoverageFrom": [
      "<rootDir>/src/components/**/*.{js,jsx,ts,tsx}",
      "<rootDir>/src/utils/**/*.{js,jsx,ts,tsx}"
    ]
  }

如果你在這一步出錯了达吞,請刪除 package-lock.json 或 yarn-lock.json 文件,在刪除 node_modules 文件夾荒典,并重新 cnpm install酪劫,這可能是腳手架的bug

image.png

這就是一個簡單的組件渲染,并且我們觸發(fā)了事件∷露現(xiàn)在測試覆蓋率是 100%
Statements覆糟、Branches 、Functions遮咖、 Lines 滩字、Uncovered Line
語句、分支、函數(shù)麦箍、行酗电、未覆蓋線
然后在項目根目錄會生成一個 coverage 文件,里面會有代碼覆蓋率内列。

怎樣看沒執(zhí)行的代碼呢,我們現(xiàn)在注釋掉以下代碼
    // test('測試添加', () => {
    //  const wrapper = shallow(<Counter />);
    //  const additionButton = wrapper.find('button').at(0);
    //  additionButton.simulate('click');
    //  expect(wrapper.state().value).toBe(1);
    // }); 

現(xiàn)在重新生成報告背率,可以看到话瞧,函數(shù)覆蓋率為 60,第10行代碼沒執(zhí)行到

image.png

另外用瀏覽器打開 coverage\lcov-report\components\Counter\index.js.html 文件(如果不打開這個文件寝姿,是無法找到函數(shù)的調(diào)用交排,另外將coverage目錄添加到.gitignore里面,防止git提交)饵筑,可以看到紅色的就是沒有被執(zhí)行到的埃篓,這里有一個 addition 方法沒有被執(zhí)行到,第10行代碼沒有被執(zhí)行到根资,需要修改測試用例的執(zhí)行架专。這里可以得知,這個是基于我們寫的測試用例代碼來判斷這個組件的功能玄帕,不是實際應(yīng)用的場景部脚,如果你的功能就這兩個,又被添加了其他方法沒有使用裤纹,那么這個時候把沒有使用的代碼進(jìn)行刪除委刘。


image.png

現(xiàn)在添加一個test方法并重新執(zhí)行,可以看到 test 方法標(biāo)紅了鹰椒,這是多余的方法锡移,并且 Funcs 覆蓋率為 83


image.png
image.png

現(xiàn)在添加兩個生命周期進(jìn)行測試,測試用例不變漆际,說明 componentDidUpdate 是生效了的淆珊,因為我們觸發(fā)了
事件,會去setState奸汇,如果我們沒有去寫按鈕的測試用例套蒂,那么這個方法就是無用的,componentDidCatch 是因為組件沒有被出錯茫蛹,所以沒有被調(diào)用到操刀。


image.png
image.png

基本上這是一個react簡單的測試用例了,enzyme api還有很多婴洼,寫之前盡量看了文檔在入手骨坑,會少走很多彎路。https://enzymejs.github.io/enzyme/docs/api/

對于網(wǎng)絡(luò)請求,這是我們需要mock的

jest https://jestjs.io/docs/en/bypassing-module-mocks
axios可以參考 https://www.robinwieruch.de/axios-jest
fetch 可以參考 https://medium.com/@rishabhsrao/mocking-and-testing-fetch-with-jest-c4d670e2e167

我們在src下新建services/index.js 文件
需要先 cnpm install --save axios

services/index.js

import axios from 'axios';

export const counterValue = () => {
    return axios.get('/counterValue.json');
};

我們繼續(xù)添加一個生命周期 componentDidMount 初始化的時候去網(wǎng)絡(luò)請求數(shù)據(jù)欢唾,并且設(shè)置為我們的初始值且警。

import React, { PureComponent } from 'react';
import { counterValue } from '../../services'

class Index extends PureComponent {

    state = {
        value: 0
    };

    async componentDidMount() {
        await this.fetchData();
    }

    componentDidUpdate() {
        console.log('更新');
    }

    fetchData = async () => {
        const { data, success } = await counterValue();
        success && this.setState({ value: data });
    };

    addition = () => {
        this.setState(({ value }) => ({ value: value + 1 }));
    };

    subtraction = () => {
        this.setState(({ value }) => ({ value: value - 1 }));
    };

    render() {
        const { value } = this.state;
        return (
            <div>
                <button onClick={this.addition}>+</button>
                <span>{value}</span>
                <button onClick={this.subtraction}>-</button>
            </div>
        )
    }
}

export default Index;

那么這個時候我們的組件有了請求,但實際跑測試用例的時候是不需要去真正的請求的礁遣,所以我們需要自己來偽造這個請求斑芜。修改一下測試用例代碼

import React from 'react';
import { shallow } from 'enzyme';
import axios from 'axios';
import Counter from './index';

// jest 內(nèi)部對 axios 進(jìn)行一些特殊處理
jest.mock('axios');
// 接口成功返回值
const successResult = {
    success: true,
    data: 2
};
// 接口失敗返回值
const errorResult = {
    success: false
};

describe('測試 Counter 組件', () => {
    // 在運(yùn)行此文件中的每個測試之前運(yùn)行一個函數(shù)
    beforeEach(() => {
        // 自己來mock axios.get 的返回值,避免網(wǎng)絡(luò)請求
        axios.get.mockReturnValue(new Promise((resolve) => resolve(successResult)));
    });
  // 注意這里添加了 async 祟霍, 因為我們的組件 componentDidMount 的時候去發(fā)起了請求
  // 所以這里需要異步杏头,然后在進(jìn)行后續(xù)操作,如果沒有寫 async await 沸呐,那么這里渲染的就是初始值
    test('測試渲染內(nèi)容醇王,初始化接口成功', async () => {
        const wrapper = await shallow(<Counter />);
        expect(wrapper.state().value).toBe(2);
        expect(wrapper.find('div')).not.toBeNull();
        expect(wrapper.find('button').length).toBe(2);
        expect(wrapper.find('span').length).toBe(1);
    });
  // 測試接口失敗的渲染
    test('測試渲染內(nèi)容,初始化接口失敗', async () => {
        axios.get.mockReturnValue(new Promise((resolve) => resolve(errorResult)));
        const wrapper = await shallow(<Counter />);
        expect(wrapper.state().value).toBe(0);
    });
  // 觸發(fā)事件也是需要這樣
    test('測試添加', async () => {
        const wrapper = await shallow(<Counter />);
        const additionButton = wrapper.find('button').at(0);
        additionButton.simulate('click');
        expect(wrapper.state().value).toBe(successResult.data + 1);
    });

    test('測試減少', async () => {
        const wrapper = await shallow(<Counter />);
        const subtractionButton = wrapper.find('button').at(1);
        subtractionButton.simulate('click');
        expect(wrapper.state().value).toBe(successResult.data - 1);
    });

});

react-testing-library

https://create-react-app.dev/docs/running-tests
https://testing-library.com/docs/react-testing-library/api#asfragment

在哪之前需要安裝
npm install --save @testing-library/react @testing-library/jest-dom

src/setupTests.js
import '@testing-library/jest-dom/extend-expect';

修改 src/components/Counter/index.js 為之前的代碼

import React, { PureComponent } from 'react';

class Index extends PureComponent {

    state = {
        value: 0
    };

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log(prevState);
    }

    addition = () => {
        this.setState(({ value }) => ({ value: value + 1 }));
    };

    subtraction = () => {
        this.setState(({ value }) => ({ value: value - 1 }));
    };

    render() {
        const { value } = this.state;
        return (
            <div>
                <button onClick={this.addition}>+</button>
                <span>{value}</span>
                <button onClick={this.subtraction}>-</button>
            </div>
        )
    }
}

export default Index;

測試用例

import React from 'react';
// 這里導(dǎo)包的文件修改
import { render } from '@testing-library/react';
import Counter from './index';

describe('測試 Counter 組件', () => {

    test('測試渲染內(nèi)容', () => {
        //  該render方法返回一個具有一些屬性的對象
        // container.querySelector 就是原生js的查找方法崭添,這里來查找到是否有這些元素
        //  如果你想判斷 state 或許需要使用 ref 來拿到react實例 
        // <Counter ref={xxx} /> ref 是你的變量
        const { container } = render(<Counter />);
        expect(container.querySelector('div')).not.toBe(null);
        expect(container.querySelectorAll('button').length).toBe(2);
        expect(container.querySelectorAll('span').length).toBe(1);
    });

    test('測試添加', () => {
        const { container } = render(<Counter />);
        const additionButton = container.querySelectorAll('button')[0];
        // 這里查找到該按鈕寓娩,然后手動觸發(fā)了 .click() 事件,這不是testing-library的方法呼渣,這就是一個
        // 原生對象自帶的方法
        additionButton.click();
        expect(container.querySelector('span').innerHTML).toBe('1');
    });

    test('測試減少', () => {
        const { container } = render(<Counter />);
        const subtractionButton = container.querySelectorAll('button')[1];
        subtractionButton.click();
        // 這里查找到span元素棘伴,判斷 innerHTML 值
        expect(container.querySelector('span').innerHTML).toBe('-1');
        subtractionButton.click();
        expect(container.querySelector('span').innerHTML).toBe('-2');
    });

});

這個和 enzyme 或許概念上有些不同,但是兩者都是可以進(jìn)行測試用例屁置,testing-library 不止支持 react排嫌。
對于接口的請求是一樣的 添加 async 和 await 就行

import React, { PureComponent } from 'react';
import { counterValue } from '../../services';

class Index extends PureComponent {

    state = {
        value: 0
    };

    async componentDidMount() {
        await this.fetchData();
    }

    fetchData = async () => {
        const { data, success } = await counterValue();
        success && this.setState({ value: data });
    };

    addition = () => {
        this.setState(({ value }) => ({ value: value + 1 }));
    };

    subtraction = () => {
        this.setState(({ value }) => ({ value: value - 1 }));
    };

    render() {
        const { value } = this.state;
        return (
            <div>
                <button onClick={this.addition}>+</button>
                <span>{value}</span>
                <button onClick={this.subtraction}>-</button>
            </div>
        )
    }
}

export default Index;

import React from 'react';
import { render } from '@testing-library/react';
import axios from 'axios';
import Counter from './index';

// jest 內(nèi)部對 axios 進(jìn)行一些特殊處理
jest.mock('axios');

const successResult = {
    success: true,
    data: 2
};

describe('測試 Counter 組件', () => {

    // 在運(yùn)行此文件中的每個測試之前運(yùn)行一個函數(shù)
    beforeEach(() => {
        // 自己來mock axios.get 的返回值,避免網(wǎng)絡(luò)請求
        axios.get.mockReturnValue(new Promise((resolve) => resolve(successResult)));
    });

    test('測試渲染內(nèi)容', () => {
        const { container } = render(<Counter />);
        expect(container.querySelector('div')).not.toBe(null);
        expect(container.querySelectorAll('button').length).toBe(2);
        expect(container.querySelectorAll('span').length).toBe(1);
    });

    test('測試添加', async () => {
        const { container } = await render(<Counter />);
        const additionButton = container.querySelectorAll('button')[0];
        additionButton.click();
        // 這里修改為 3
        expect(container.querySelector('span').innerHTML).toBe('3');
    });

    test('測試減少', async () => {
        const { container } = await render(<Counter />);
        const subtractionButton = container.querySelectorAll('button')[1];
        subtractionButton.click();
        // 這里修改為 1
        expect(container.querySelector('span').innerHTML).toBe('1');
        subtractionButton.click();
        // 這里修改為 0
        expect(container.querySelector('span').innerHTML).toBe('0');
    });

});

image.png

其他更多的方法調(diào)用請參考官網(wǎng)
https://testing-library.com/docs/react-testing-library/api#render-result

相關(guān)參考

  1. 如果你需要 redux 測試請參考 https://www.redux.org.cn/docs/recipes/WritingTests.html
  2. jest官網(wǎng) https://jestjs.io/docs/en/getting-started
  3. create-react-app測試用例 https://www.html.cn/create-react-app/docs/running-tests/
  4. react測試用例 https://zh-hans.reactjs.org/docs/testing.html
  5. enzyme文檔 https://enzymejs.github.io/enzyme/
  6. react-testing-library文檔 https://testing-library.com/docs/react-testing-library/intro
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缰犁,一起剝皮案震驚了整個濱河市淳地,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帅容,老刑警劉巖颇象,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異并徘,居然都是意外死亡遣钳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門麦乞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕴茴,“玉大人,你說我怎么就攤上這事姐直【氲恚” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵声畏,是天一觀的道長撞叽。 經(jīng)常有香客問我姻成,道長,這世上最難降的妖魔是什么愿棋? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任科展,我火速辦了婚禮,結(jié)果婚禮上糠雨,老公的妹妹穿的比我還像新娘才睹。我一直安慰自己,他們只是感情好甘邀,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布琅攘。 她就那樣靜靜地躺著,像睡著了一般鹃答。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上突硝,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天测摔,我揣著相機(jī)與錄音,去河邊找鬼解恰。 笑死锋八,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的护盈。 我是一名探鬼主播挟纱,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腐宋!你這毒婦竟也來了紊服?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤胸竞,失蹤者是張志新(化名)和其女友劉穎欺嗤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫枝,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡煎饼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了校赤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吆玖。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖马篮,靈堂內(nèi)的尸體忽然破棺而出沾乘,到底是詐尸還是另有隱情,我是刑警寧澤浑测,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布意鲸,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怎顾。R本人自食惡果不足惜读慎,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望槐雾。 院中可真熱鬧夭委,春花似錦、人聲如沸募强。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擎值。三九已至慌烧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸠儿,已是汗流浹背屹蚊。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留进每,地道東北人汹粤。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像田晚,于是被迫代替她去往敵國和親嘱兼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345