日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

全網最細:Jest+Enzyme測試React組件(包含交互、DOM、樣式測試)

來源: 責編: 時間:2023-11-30 09:29:04 226觀看
導讀介紹Jest是目前前端工程化下單元測試火熱的技術棧,而Enzyme的支持提供了Jest測試React業務、組件的能力,下面來介紹一下React組件測試的一些實際場景。1. 測試依賴包"enzyme": "^3.11.0", "enzyme-adapter-react-16"

介紹

Jest是目前前端工程化下單元測試火熱的技術棧,而Enzyme的支持提供了Jest測試React業務、組件的能力,下面來介紹一下React組件測試的一些實際場景。dqU28資訊網——每日最新資訊28at.com

1. 測試依賴包

"enzyme": "^3.11.0",    "enzyme-adapter-react-16": "^1.15.2",    "enzyme-to-json": "^3.3.5",    "jest": "^28.1.1",    "jest-less-loader": "^0.1.2",    "jsdom": "^19.0.0",   //解決mount渲染組件失敗的BUG,具體見上文    "ts-jest": "^28.0.5",

2. 測試環境搭建

由于enzyme的配置在每次需要測試組件時都需要加入,因此配置setup.js后在每次測試組件中提前引入是不錯的選擇。dqU28資訊網——每日最新資訊28at.com

setup.js:dqU28資訊網——每日最新資訊28at.com

import Enzyme from 'enzyme';import Adapter from 'enzyme-adapter-react-16';const jsdom = require('jsdom');//解決無法mount渲染組件的問題const { JSDOM } = jsdom;const { window } = new JSDOM('');const { document } = new JSDOM(``).window;global.document = document;global.window = window;//初始化配置Enzyme.configure({  adapter: new Adapter(),});export default Enzyme;

jest.config.js配置:dqU28資訊網——每日最新資訊28at.com

module.exports = {  transform: {    '^.+//.(ts|tsx|js|jsx)?$': 'ts-jest',    '//.(less|css)$': 'jest-less-loader', // 支持less  },  testRegex: '(/__tests__/.*|(//.|/)(test|spec))//.(jsx?|tsx?)$',    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],};

3. 組件基礎渲染測試

在為組件添加prop傳值之前,可配置一個基礎的 mountTest.tsx 來對組件進行一個基礎渲染掛載測試,測試通過后在進行復雜情況下的測試。dqU28資訊網——每日最新資訊28at.com

mountTest.tsxdqU28資訊網——每日最新資訊28at.com

import React from 'react';import { mount } from 'enzyme';// 此處Component的類型存在疑問,待完善export default function mountTest(Component: React.ComponentType<any" data-textnode-index-1701226829723="160" data-index-1701226829723="1307" data-index-len-1701226829723="1307" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> | React.ComponentType) {  describe(`mount and unmount`, () =" data-textnode-index-1701226829723="167" data-index-1701226829723="1369" data-index-len-1701226829723="1369" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    it(`component could be updated and unmounted without errors`, () =" data-textnode-index-1701226829723="173" data-index-1701226829723="1442" data-index-len-1701226829723="1442" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      const wrapper = mount(<Component /" data-textnode-index-1701226829723="177" data-index-1701226829723="1485" data-index-len-1701226829723="1485" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);      expect(() =" data-textnode-index-1701226829723="180" data-index-1701226829723="1505" data-index-len-1701226829723="1505" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {        wrapper.setProps({});        wrapper.unmount();      }).not.toThrow();    });  });}

4. 組件交互相關測試

Button按鈕組件測試

這里拿Button按鈕舉例,具體Button組件可在http://react-view-ui.com:92/#/common/button參考,底部描述了組件的API能力。dqU28資訊網——每日最新資訊28at.com

圖片圖片dqU28資訊網——每日最新資訊28at.com

先看一下Button組件的整體測試文件,我一共分成了4組測試用例(不包含mountTest基礎測試)。dqU28資訊網——每日最新資訊28at.com

Button.test.tsxdqU28資訊網——每日最新資訊28at.com

import React from 'react';import Button from '../../Button/index';import Enzyme from '../setup';import mountTest from '../mountTest';import { act } from 'react-dom/test-utils';const { shallow, mount } = Enzyme;mountTest(Button);describe(`button`, () =" data-textnode-index-1701226829723="230" data-index-1701226829723="2025" data-index-len-1701226829723="2025" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {  it('button children show correctly', () =" data-textnode-index-1701226829723="236" data-index-1701226829723="2071" data-index-len-1701226829723="2071" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //按鈕文字內容測試    const component = shallow(<Button" data-textnode-index-1701226829723="242" data-index-1701226829723="2125" data-index-len-1701226829723="2125" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>testButton</Button" data-textnode-index-1701226829723="243" data-index-1701226829723="2144" data-index-len-1701226829723="2144" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    const button = component.find('.button');    const p = button.find('button');    expect(p.text()).toBe('testButton');  });  it('click callback correctly', () =" data-textnode-index-1701226829723="248" data-index-1701226829723="2310" data-index-len-1701226829723="2310" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //按鈕點擊回調測試    const mockFn = jest.fn();    const component = shallow(<Button handleClick={mockFn} /" data-textnode-index-1701226829723="253" data-index-1701226829723="2416" data-index-len-1701226829723="2416" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    const button = component.find('.button');    button.simulate('click');    const mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);    act(() =" data-textnode-index-1701226829723="270" data-index-1701226829723="2596" data-index-len-1701226829723="2596" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      //測禁用按鈕      component.setProps({        disabled: true,      });    });    button.simulate('click');    expect(mockFn.mock.calls.length).toBe(mockFnCallLength);  });  it('button type set show correctly color', () =" data-textnode-index-1701226829723="289" data-index-1701226829723="2820" data-index-len-1701226829723="2820" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試按鈕type被賦值className    const component = mount(<Button type="primary" /" data-textnode-index-1701226829723="299" data-index-1701226829723="2901" data-index-len-1701226829723="2901" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('button').hasClass('primary')).toBe(true);  });  it('button loading show correctly', () =" data-textnode-index-1701226829723="312" data-index-1701226829723="3019" data-index-len-1701226829723="3019" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試加載按鈕顯示    const component = mount(<Button type="primary" loading /" data-textnode-index-1701226829723="322" data-index-1701226829723="3096" data-index-len-1701226829723="3096" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('loading1')).not.toBeUndefined();  });});

從代碼中可以看到,初始化配置一共有如下代碼:dqU28資訊網——每日最新資訊28at.com

import React from 'react';import Button from '../../Button/index';import Enzyme from '../setup';import mountTest from '../mountTest';import { act } from 'react-dom/test-utils';const { shallow, mount } = Enzyme;mountTest(Button);

主要功能:引入必要的包、引入測試組件、引入組件渲染方式,這是是shallow和mount兩種,并在最后優先進行了組件基礎渲染測試。dqU28資訊網——每日最新資訊28at.com

第一組測試用例測試了Button按鈕的文字顯示正確性,是通過jest的find方法查詢到Button按鈕的DOM元素進行判斷;之后設置了組件的disabled屬性,再次進行點擊測試dqU28資訊網——每日最新資訊28at.com

it('button children show correctly', () =" data-textnode-index-1701226829723="372" data-index-1701226829723="3612" data-index-len-1701226829723="3612" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //按鈕文字內容測試    const component = shallow(<Button" data-textnode-index-1701226829723="378" data-index-1701226829723="3666" data-index-len-1701226829723="3666" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>testButton</Button" data-textnode-index-1701226829723="379" data-index-1701226829723="3685" data-index-len-1701226829723="3685" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    const button = component.find('.button');    const p = button.find('button');    expect(p.text()).toBe('testButton');});

第二組測試用例測試了按鈕的交互,在渲染組件之后,捕捉到按鈕的DOM,并自定義了mockFn函數傳遞給實際Button組件后進行回調測試,Button我在點擊時是沒有傳參的,因此回調參數長度為0dqU28資訊網——每日最新資訊28at.com

it('click callback correctly', () =" data-textnode-index-1701226829723="389" data-index-1701226829723="3943" data-index-len-1701226829723="3943" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //按鈕點擊回調測試    const mockFn = jest.fn();    const component = shallow(<Button handleClick={mockFn} /" data-textnode-index-1701226829723="398" data-index-1701226829723="4049" data-index-len-1701226829723="4049" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    const button = component.find('.button');    button.simulate('click');    const mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);    act(() =" data-textnode-index-1701226829723="415" data-index-1701226829723="4229" data-index-len-1701226829723="4229" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      //測禁用按鈕      component.setProps({        disabled: true,      });    });    button.simulate('click');    expect(mockFn.mock.calls.length).toBe(mockFnCallLength);});

第三組測試用例對Button按鈕類型進行了測試,傳遞了type:primary,并對渲染后的組件進行判斷是否有primary的類名dqU28資訊網——每日最新資訊28at.com

it('button type set show correctly color', () =" data-textnode-index-1701226829723="435" data-index-1701226829723="4514" data-index-len-1701226829723="4514" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試按鈕type被賦值className    const component = mount(<Button type="primary" /" data-textnode-index-1701226829723="445" data-index-1701226829723="4595" data-index-len-1701226829723="4595" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('button').hasClass('primary')).toBe(true);});

第四組測試用例對loading Button進行了測試,同樣也是檢查類名的形式,與第三組測試用例類似dqU28資訊網——每日最新資訊28at.com

it('button loading show correctly', () =" data-textnode-index-1701226829723="459" data-index-1701226829723="4759" data-index-len-1701226829723="4759" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試加載按鈕顯示    const component = mount(<Button type="primary" loading /" data-textnode-index-1701226829723="469" data-index-1701226829723="4836" data-index-len-1701226829723="4836" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('loading1')).not.toBeUndefined();  });

這就是我對Button的測試。dqU28資訊網——每日最新資訊28at.com

Avatar頭像組件測試

由于Button組件本身功能比較簡單,可擴展性有限,作為第一個組件案例進行舉例。dqU28資訊網——每日最新資訊28at.com

接下來對Avatar組件進行測試。dqU28資訊網——每日最新資訊28at.com

圖片圖片dqU28資訊網——每日最新資訊28at.com

還是先上測試源碼。dqU28資訊網——每日最新資訊28at.com

Avatar.test.tsx:dqU28資訊網——每日最新資訊28at.com

import React, { ReactNode } from 'react';import ReactDOM from 'react-dom';import Avatar from '../../Avatar/index';import AvatarGroup from '../../Avatar/group';import { CameraOutlined } from '@ant-design/icons';import Enzyme from '../setup';import mountTest from '../mountTest';import { act } from 'react-dom/test-utils';const { mount } = Enzyme;let container: HTMLDivElement | null;mountTest(Avatar);describe('Avatar', () =" data-textnode-index-1701226829723="539" data-index-1701226829723="5435" data-index-len-1701226829723="5435" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {  //測試前準備容器  beforeEach(() =" data-textnode-index-1701226829723="545" data-index-1701226829723="5466" data-index-len-1701226829723="5466" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    container = document.createElement('div');    document.body.appendChild(container);  });  //測試后刪除容器  afterEach(() =" data-textnode-index-1701226829723="560" data-index-1701226829723="5588" data-index-len-1701226829723="5588" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    document.body.removeChild(container as HTMLDivElement);    container = null;  });  it('test avatar children content show correctly', () =" data-textnode-index-1701226829723="575" data-index-1701226829723="5732" data-index-len-1701226829723="5732" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試頭像文本顯示    let contextText: string | ReactNode = 'test';    const component = mount(<Avatar" data-textnode-index-1701226829723="588" data-index-1701226829723="5833" data-index-len-1701226829723="5833" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>{contextText}</Avatar" data-textnode-index-1701226829723="589" data-index-1701226829723="5855" data-index-len-1701226829723="5855" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('.text-ref').text()).toEqual('test');    const imgSrc =      'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png';    act(() =" data-textnode-index-1701226829723="605" data-index-1701226829723="6059" data-index-len-1701226829723="6059" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      contextText = <img src={imgSrc}" data-textnode-index-1701226829723="606" data-index-1701226829723="6099" data-index-len-1701226829723="6099" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">></img" data-textnode-index-1701226829723="606" data-index-1701226829723="6105" data-index-len-1701226829723="6105" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>;    });    expect(component.find('img')).toBeDefined();  });  it('test avatar group correctly', () =" data-textnode-index-1701226829723="614" data-index-1701226829723="6207" data-index-len-1701226829723="6207" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試頭像樣式    const component = (      <AvatarGroup size={50} groupStyle={{ margin: '0 10px' }}" data-textnode-index-1701226829723="622" data-index-1701226829723="6307" data-index-len-1701226829723="6307" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(20, 169, 248)' }} shape="square"" data-textnode-index-1701226829723="631" data-index-1701226829723="6382" data-index-len-1701226829723="6382" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>          View        </Avatar" data-textnode-index-1701226829723="633" data-index-1701226829723="6413" data-index-len-1701226829723="6413" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(51, 112, 255)' }}" data-textnode-index-1701226829723="642" data-index-1701226829723="6473" data-index-len-1701226829723="6473" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>React</Avatar" data-textnode-index-1701226829723="642" data-index-1701226829723="6487" data-index-len-1701226829723="6487" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(0, 208, 184)' }}" data-textnode-index-1701226829723="651" data-index-1701226829723="6546" data-index-len-1701226829723="6546" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>UI</Avatar" data-textnode-index-1701226829723="651" data-index-1701226829723="6557" data-index-len-1701226829723="6557" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>      </AvatarGroup" data-textnode-index-1701226829723="652" data-index-1701226829723="6577" data-index-len-1701226829723="6577" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>    );    act(() =" data-textnode-index-1701226829723="654" data-index-1701226829723="6596" data-index-len-1701226829723="6596" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      ReactDOM.render(component, container);    });    const avatarStyleList = [      {        background: 'rgb(20, 169, 248)',        content: 'View',      },      {        background: 'rgb(51, 112, 255)',        content: 'React',      },      {        background: 'rgb(0, 208, 184)',        content: 'UI',      },    ];    const groupDom = (container as HTMLDivElement).querySelector('.avatar-group') as HTMLElement;    expect(groupDom.childElementCount).toBe(3);    const avatars = Array.from((container as HTMLDivElement).querySelectorAll('.avatar'));    avatars.forEach((avatar, index) =" data-textnode-index-1701226829723="708" data-index-1701226829723="7191" data-index-len-1701226829723="7191" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      //測試頭像組的每個頭像樣式      expect(        avatar          .getAttribute('style')          ?.includes(`background: ${avatarStyleList[index].background}`) &&          avatar.querySelector('.text-ref')?.innerHTML === avatarStyleList[index].content,      ).toBe(true);      if (index === 0) {        //測試頭像形狀        expect(avatar.getAttribute('style')?.includes(`border-radius: 5px`)).toBe(true);      }    });  });  it('test avatar click callback correctly', () =" data-textnode-index-1701226829723="730" data-index-1701226829723="7653" data-index-len-1701226829723="7653" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //頭像點擊交互測試    const mockFn = jest.fn();    const component = mount(      <Avatar        size={54}        triggerType="mask"        triggerIcon={<CameraOutlined style={{ fontSize: '20px' }} /" data-textnode-index-1701226829723="740" data-index-1701226829723="7850" data-index-len-1701226829723="7850" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>}        triggerClick={mockFn}      " data-textnode-index-1701226829723="742" data-index-1701226829723="7887" data-index-len-1701226829723="7887" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <img src="https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png"" data-textnode-index-1701226829723="743" data-index-1701226829723="8006" data-index-len-1701226829723="8006" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">></img" data-textnode-index-1701226829723="743" data-index-1701226829723="8012" data-index-len-1701226829723="8012" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>      </Avatar" data-textnode-index-1701226829723="744" data-index-1701226829723="8027" data-index-len-1701226829723="8027" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>,    );    act(() =" data-textnode-index-1701226829723="746" data-index-1701226829723="8047" data-index-len-1701226829723="8047" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      component.simulate('click');    });    let mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);    act(() =" data-textnode-index-1701226829723="753" data-index-1701226829723="8192" data-index-len-1701226829723="8192" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      component.setProps({        triggerType: 'button',      });    });    component.update();    mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);  });});

拆解一下組件的源碼,測試最初的操作如下:dqU28資訊網——每日最新資訊28at.com

import React, { ReactNode } from 'react';import ReactDOM from 'react-dom';import Avatar from '../../Avatar/index';import AvatarGroup from '../../Avatar/group';import { CameraOutlined } from '@ant-design/icons';import Enzyme from '../setup';import mountTest from '../mountTest';import { act } from 'react-dom/test-utils';const { mount } = Enzyme;let container: HTMLDivElement | null;mountTest(Avatar);

和Button的測試區別點其實就在,定義了container容器,用于接下來的DOM測試。dqU28資訊網——每日最新資訊28at.com

//測試前準備容器  beforeEach(() =" data-textnode-index-1701226829723="825" data-index-1701226829723="8874" data-index-len-1701226829723="8874" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    container = document.createElement('div');    document.body.appendChild(container);  });  //測試后刪除容器  afterEach(() =" data-textnode-index-1701226829723="840" data-index-1701226829723="8996" data-index-len-1701226829723="8996" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    document.body.removeChild(container as HTMLDivElement);    container = null;  });

在進行測試用例之前,創建了一個空div作為React測試的容器,放置React組件,并在測試用例結束后對該容器進行清除。dqU28資訊網——每日最新資訊28at.com

接下來我們開始分析測試用例:dqU28資訊網——每日最新資訊28at.com

第一組測試用例測試了文本頭像和圖片頭像的顯示正確性,首先給組件傳遞了一個test文本值,對文本值進行判斷。之后又給組件傳遞了一張圖片(ReactNode),并對組件中的圖片進行查詢判斷。dqU28資訊網——每日最新資訊28at.com

it('test avatar children content show correctly', () =" data-textnode-index-1701226829723="858" data-index-1701226829723="9305" data-index-len-1701226829723="9305" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試頭像文本顯示    let contextText: string | ReactNode = 'test';    const component = mount(<Avatar" data-textnode-index-1701226829723="871" data-index-1701226829723="9406" data-index-len-1701226829723="9406" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>{contextText}</Avatar" data-textnode-index-1701226829723="872" data-index-1701226829723="9428" data-index-len-1701226829723="9428" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>);    expect(component.find('.text-ref').text()).toEqual('test');    const imgSrc =      'https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png';    act(() =" data-textnode-index-1701226829723="888" data-index-1701226829723="9632" data-index-len-1701226829723="9632" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      contextText = <img src={imgSrc}" data-textnode-index-1701226829723="889" data-index-1701226829723="9672" data-index-len-1701226829723="9672" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">></img" data-textnode-index-1701226829723="889" data-index-1701226829723="9678" data-index-len-1701226829723="9678" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>;    });    expect(component.find('img')).toBeDefined();});

第二組測試用例較為復雜,沒有通過jest的渲染方式渲染組件,而是用上了之前所講到的container容器,并且創建了一個React虛擬DOM,渲染在測試用例環境中。這樣做其實也是因為測試用例本身是需要測試不同情況下的頭像樣式是否生效,因此會用到這種渲染方式。dqU28資訊網——每日最新資訊28at.com

it('test avatar group correctly', () =" data-textnode-index-1701226829723="900" data-index-1701226829723="9905" data-index-len-1701226829723="9905" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //測試頭像樣式    const component = (      <AvatarGroup size={50} groupStyle={{ margin: '0 10px' }}" data-textnode-index-1701226829723="911" data-index-1701226829723="10005" data-index-len-1701226829723="10005" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(20, 169, 248)' }} shape="square"" data-textnode-index-1701226829723="916" data-index-1701226829723="10080" data-index-len-1701226829723="10080" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>          View        </Avatar" data-textnode-index-1701226829723="919" data-index-1701226829723="10111" data-index-len-1701226829723="10111" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(51, 112, 255)' }}" data-textnode-index-1701226829723="920" data-index-1701226829723="10171" data-index-len-1701226829723="10171" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>React</Avatar" data-textnode-index-1701226829723="921" data-index-1701226829723="10185" data-index-len-1701226829723="10185" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <Avatar style={{ background: 'rgb(0, 208, 184)' }}" data-textnode-index-1701226829723="924" data-index-1701226829723="10244" data-index-len-1701226829723="10244" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>UI</Avatar" data-textnode-index-1701226829723="925" data-index-1701226829723="10255" data-index-len-1701226829723="10255" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>      </AvatarGroup" data-textnode-index-1701226829723="927" data-index-1701226829723="10275" data-index-len-1701226829723="10275" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>    );    act(() =" data-textnode-index-1701226829723="931" data-index-1701226829723="10294" data-index-len-1701226829723="10294" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      ReactDOM.render(component, container);    });    const avatarStyleList = [      {        background: 'rgb(20, 169, 248)',        content: 'View',      },      {        background: 'rgb(51, 112, 255)',        content: 'React',      },      {        background: 'rgb(0, 208, 184)',        content: 'UI',      },    ];    const groupDom = (container as HTMLDivElement).querySelector('.avatar-group') as HTMLElement;    expect(groupDom.childElementCount).toBe(3);    const avatars = Array.from((container as HTMLDivElement).querySelectorAll('.avatar'));    avatars.forEach((avatar, index) =" data-textnode-index-1701226829723="987" data-index-1701226829723="10889" data-index-len-1701226829723="10889" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      //測試頭像組的每個頭像樣式      expect(        avatar          .getAttribute('style')          ?.includes(`background: ${avatarStyleList[index].background}`) &&          avatar.querySelector('.text-ref')?.innerHTML === avatarStyleList[index].content,      ).toBe(true);      if (index === 0) {        //測試頭像形狀        expect(avatar.getAttribute('style')?.includes(`border-radius: 5px`)).toBe(true);      }    });  });

通過ReactDOM.render渲染后,首先獲取了所有頭像的最外層容器:groupDom,并對頭像組所包含的頭像元素長度進行判斷,我這里是傳了三個頭像,因此預期應該為3。dqU28資訊網——每日最新資訊28at.com

const groupDom = (container as HTMLDivElement).querySelector('.avatar-group') as HTMLElement;expect(groupDom.childElementCount).toBe(3);

接下來獲取了所有頭像的DOM,并進行遍歷判斷,判斷自定義的頭像背景顏色和所傳文本內容是否相同,兩者都滿足,則該頭像的測試通過;并在我對第一個頭像設置了shape: square,這代表了這是一個方形頭像,因此在遍歷中需要對第一個頭像單獨做一次測試,判斷它的樣式是否生效(圓角)dqU28資訊網——每日最新資訊28at.com

avatars.forEach((avatar, index) =" data-textnode-index-1701226829723="1042" data-index-1701226829723="11695" data-index-len-1701226829723="11695" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      //測試頭像組的每個頭像樣式      expect(        avatar          .getAttribute('style')          ?.includes(`background: ${avatarStyleList[index].background}`) &&          avatar.querySelector('.text-ref')?.innerHTML === avatarStyleList[index].content,      ).toBe(true);      if (index === 0) {        //測試頭像形狀        expect(avatar.getAttribute('style')?.includes(`border-radius: 5px`)).toBe(true);      }    });

如上就是第二組測試用例,和之前測試用例不同的無非就是渲染方式和組件的樣式判斷,使用了原生的一些判斷,最后通過jest的toBe方法進行斷言。dqU28資訊網——每日最新資訊28at.com

第三組測試用例是交互測試,在對頭像設置了triggerIcon、triggerType、triggerClick后可變成交互頭像,具體顯示可查看組件庫文檔-Avatar頭像。這里也是先定義了一個mock函數,傳遞給組件作為回調函數測試,并且整體測試了mask、button兩種交互頭像的回調正確性dqU28資訊網——每日最新資訊28at.com

it('test avatar click callback correctly', () =" data-textnode-index-1701226829723="1088" data-index-1701226829723="12368" data-index-len-1701226829723="12368" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {    //頭像點擊交互測試    const mockFn = jest.fn();    const component = mount(      <Avatar        size={54}        triggerType="mask"        triggerIcon={<CameraOutlined style={{ fontSize: '20px' }} /" data-textnode-index-1701226829723="1106" data-index-1701226829723="12565" data-index-len-1701226829723="12565" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>}        triggerClick={mockFn}      " data-textnode-index-1701226829723="1108" data-index-1701226829723="12602" data-index-len-1701226829723="12602" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>        <img src="https://user-images.githubusercontent.com/9554297/83762004-a0761b00-a6a9-11ea-83b4-9c8ff721d4b8.png"" data-textnode-index-1701226829723="1111" data-index-1701226829723="12721" data-index-len-1701226829723="12721" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">></img" data-textnode-index-1701226829723="1112" data-index-1701226829723="12727" data-index-len-1701226829723="12727" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>      </Avatar" data-textnode-index-1701226829723="1114" data-index-1701226829723="12742" data-index-len-1701226829723="12742" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">>,    );    act(() =" data-textnode-index-1701226829723="1118" data-index-1701226829723="12762" data-index-len-1701226829723="12762" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      component.simulate('click');    });    let mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);    act(() =" data-textnode-index-1701226829723="1132" data-index-1701226829723="12907" data-index-len-1701226829723="12907" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: inline-block; text-indent: initial;">> {      component.setProps({        triggerType: 'button',      });    });    component.update();    mockFnCallLength = mockFn.mock.calls.length;    expect(mockFnCallLength).toBe(0);  });

如上就是頭像組件的所有測試用例。dqU28資訊網——每日最新資訊28at.com

小結

測試React組件無非就是測試其交互性和樣式渲染正確性,因此筆者在React組件測試中使用最頻繁的就是文中所述的兩種渲染形式dqU28資訊網——每日最新資訊28at.com

  • Jest渲染(mount、render、shallow)
  • ReactDOM渲染(用于測試樣式、元素節點)

因此掌握了這兩種渲染形式去書寫測試用例,可以測試到大部分的組件業務場景,在組件上線之前mock出更多的場景來避免錯誤發生。dqU28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-35305-0.html全網最細:Jest+Enzyme測試React組件(包含交互、DOM、樣式測試)

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 只會在終端使用Python運行代碼?這些進階用法了解了解

下一篇: 從PDF和圖像中提取文本,以供大型語言模型使用

標簽:
  • 熱門焦點
  • Redmi Buds 4開箱簡評:才199還有降噪 可以無腦入

    在上個月舉辦的Redmi Note11T Pro系列新機發布會上,除了兩款手機新品之外,Redmi還帶來了兩款TWS真無線藍牙耳機產品,Redmi Buds 4和Redmi Buds 4 Pro,此前我們在Redmi Note11T
  • Rust中的高吞吐量流處理

    作者 | Noz編譯 | 王瑞平本篇文章主要介紹了Rust中流處理的概念、方法和優化。作者不僅介紹了流處理的基本概念以及Rust中常用的流處理庫,還使用這些庫實現了一個流處理程序
  • 中國家電海外掘金正當時|出海專題

    作者|吳南南編輯|胡展嘉運營|陳佳慧出品|零態LT(ID:LingTai_LT)2023年,出海市場戰況空前,中國創業者在海外紛紛摩拳擦掌,以期能夠把中國的商業模式、創業理念、戰略打法輸出海外,他們依
  • 10天營收超1億美元,《星鐵》比《原神》差在哪?

    來源:伯虎財經作者:陳平安即便你沒玩過《原神》,你一定聽說過的它的大名。恨它的人把《原神》開服那天稱作是中國游戲史上最黑暗的一天,有粉絲因為索尼在PS平臺上線《原神》,怒而
  • “又被陳思誠騙了”

    作者|張思齊 出品|眾面(ID:ZhongMian_ZM)如今的國產懸疑電影,成了陳思誠的天下。最近大爆電影《消失的她》票房突破30億斷層奪魁暑期檔,陳思誠再度風頭無兩。你可以說陳思誠的
  • 新電商三兄弟,“抖快紅”成團!

    來源:價值研究所作 者:Hernanderz 隨著內容電商的概念興起,抖音、快手、小紅書組成的&ldquo;新電商三兄弟&rdquo;成為業內一股不可忽視的勢力,給阿里、京東、拼多多帶去了巨大壓
  • 花7萬退貨退款無門:誰在縱容淘寶珠寶商家造假?

    來源:極點商業作者:楊銘在淘寶購買珠寶玉石后,因為保證金不夠賠付,店鋪關閉,退貨退款難、維權無門的比比皆是。&ldquo;提供相關產品鑒定證書,支持全國復檢,可以30天無理由退換貨。&
  • 小米公益基金會捐贈2500萬元馳援北京、河北暴雨救災

    8月2日消息,今日小米科技創始人雷軍在其微博上發布消息稱,小米公益基金會宣布捐贈2500萬元馳援北京、河北暴雨救災。攜手抗災,京冀安康!以下為公告原文
  • 三翼鳥智能家居亮相電博會,讓用戶體驗更真實

    2021電博會在青島國際會展中心開幕中,三翼鳥直接把“家”搬到了現場,成為了展會的一大看點。這也是三翼鳥繼9月9日發布了行業首個一站式定制智慧家平臺后的
Top 主站蜘蛛池模板: 柘城县| 桂平市| 临邑县| 沂水县| 普兰店市| 忻州市| 亳州市| 绵阳市| 安宁市| 阳高县| 阿尔山市| 墨脱县| 什邡市| 会宁县| 东阳市| 林周县| 丽水市| 石楼县| 巧家县| 敖汉旗| 乐至县| 息烽县| 应用必备| 青川县| 武宣县| 历史| 东明县| 余干县| 双峰县| 山东省| 静海县| 苏尼特右旗| 郁南县| 萨嘎县| 新野县| 嵊州市| 八宿县| 昌平区| 巴塘县| 阳朔县| 渑池县|