使用官方推荐的库来测react hook组件
最近写单元测试的时候遇见了一些问题,当我使用使用jest测React. useRef, React. useEffect时,总是测不到,
然后我去查阅了一下官方文档,它推荐了使用下面这个库
我来试了哈,还是不得行,于是根据这个库我在npm里头找到了相关的库,就是下面这个,专门用来测Hook的 @testing-library/react-hooks
这是我的组件BarChart
注意: handleSortByReject 是一个处理函数,我没有把代码贴出来,可以忽略掉这个方法
( 节省时间小tips ,主要关注63-75行的内容,目的是测React.useRef和React.useEffect )

1 import * as React from "react"; 2 import {Chart} from "react-chartjs-2"; 3 import {Chart as ChartJS, Title, Tooltip, Legend, CategoryScale, BarController, BarElement} from "chart.js"; 4 import {IChartDataList} from "./IChartDataList"; 5 import {ILinkPosition} from "./common"; 6 import {ILink} from "./ILink"; 7 import {IDatasetWithDetectionId} from "./IDatasetWithDetectionId"; 8 import {ItemWithLoading} from "./ItemWithLoading"; 9 10 // Register the elements to display ChartJs. Enables tree-shaking reducing bundle size. 11 // See https://www.chartjs.org/docs/latest/getting-started/integration.html#bundlers-webpack-rollup-etc 12 ChartJS.register(Title, Tooltip, Legend, CategoryScale, BarController, BarElement); 13 14 export interface ISum { 15 subscript: number; 16 totalReject: number[]; 17 label: string; 18 sumOfTotalReject?: number; 19 } 20 21 export interface IBarChartProps { 22 /** The state of showing loading icon while sending request */ 23 showLoading: boolean; 24 /** height of chart */ 25 chartHeight: number; 26 /** current bar data */ 27 itemData: IChartDataList; 28 /** sort data by label or total reject, if true, sort by total reject */ 29 sortByReject: boolean; 30 } 31 32 export const BarChart: React.FC<IBarChartProps> = (props: IBarChartProps) => { 33 const chartRef = React.useRef<ChartJS>(null); 34 35 /** 36 * @returns options property or bar chart 37 */ 38 const getOptionBarChart = (titleKey?: string) => { 39 return { 40 maintainAspectRatio: false, 41 scales: { 42 x: { 43 stacked: true, 44 }, 45 y: { 46 stacked: true, 47 }, 48 }, 49 plugins: { 50 title: { 51 display: true, 52 text: titleKey, 53 }, 54 }, 55 }; 56 }; 57 58 /** 59 * use official function to update chart, more detail you can see 60 * 1. [update.html](https://www.chartjs.org/docs/latest/developers/updates.html) 61 * 2. [chartRef-demo](https://react-chartjs-2.netlify.app/examples/chart-ref/) 62 */ 63 const updateChartData = (chart) => { 64 if (chart.data !== null && chart.data !== undefined) { 65 chart = handleSortByReject(chart); 66 chart.update(); 67 } 68 }; 69 70 React.useEffect(() => { 71 const chart = chartRef.current; 72 if (chart !== null) { 73 updateChartData(chart); 74 } 75 }, [props.sortByReject, props.itemData.chartData.labels, props.showLoading, props.chartHeight]); 76 77 return ( 78 <> 79 {/* * before get response of backend, show loading 80 * if backend return empty list, show 'no data' 81 * otherwise, render bar chart */} 82 <ItemWithLoading 83 height={props.chartHeight} 84 showLoading={props.showLoading} 85 ele={ 86 props.itemData.chartData !== undefined && 87 props.itemData.chartData.labels && 88 props.itemData.chartData.labels.length !== 0 && 89 props.itemData.chartData.datasets.length !== 0 && ( 90 /** about ref of bar chart, more detail you can see 91 * [chartRef-demo](https://react-chartjs-2.netlify.app/examples/chart-ref/) 92 */ 93 <Chart 94 type="bar" 95 ref={chartRef} 96 height={props.chartHeight} 97 data={props.itemData.chartData} 98 options={getOptionBarChart(props.itemData.titleKey)} 99 /> 100 ) 101 } 102 /> 103 </> 104 ); 105 };
这是我的组件ItemWithLoading
注意:(节省时间小tips, 可以不关注这个组件)

1 import * as React from "react"; 2 import {AppContext} from "../app/IAppContext"; 3 import {Loading} from "../app/Loading"; 4 import "../public/style/datalight/ItemWithLoading.scss"; 5 6 export interface IItemWithLoadingProps { 7 height: number; 8 /** if true, show loading icon */ 9 showLoading: boolean; 10 ele?: JSX.Element | boolean; 11 } 12 13 /** render no data */ 14 export const ItemWithLoading: React.FC<IItemWithLoadingProps> = (props: IItemWithLoadingProps) => ( 15 // before render element, show loading 16 <AppContext.Consumer> 17 {(ctx) => 18 props.showLoading ? ( 19 <Loading /> 20 ) : props.ele ? ( 21 props.ele 22 ) : ( 23 // if ele not available, show 'no data' 24 <div className="tes-datalight-noData" style={{height: props.height}}> 25 {ctx.translator.translate("tes_fe_gallery_filter_nodata")} 26 </div> 27 ) 28 } 29 </AppContext.Consumer> 30 );
这是测试
注意: 前面三个测试采用了react-test-renderer库的create方法来测渲染
第四个测试采用了renderHook方法来测update方法有没有被调用,配合jest.spyOn和jest.fn方法
(节省时间小tips, 主要关注第四个测试 )

1 import * as React from "react"; 2 import * as TypeMoq from "typemoq"; 3 import {describe, expect, it, jest} from "@jest/globals"; 4 import {ActiveElement, ChartEvent} from "chart.js"; 5 import {hotChartData, hotData, mcalMultiChartData, mxChartData, mxData} from "./panel2MockData"; 6 import {create} from "react-test-renderer"; 7 import {IChartDataList} from "../../datalight/IChartDataList"; 8 import {ILinkPosition} from "../../datalight/common"; 9 import {ILink} from "../../datalight/ILink"; 10 import {BarChart, IBarChartProps} from "../../datalight/BarChart"; 11 import {ItemWithLoading} from "../../datalight/ItemWithLoading"; 12 import {Chart} from "react-chartjs-2"; 13 import {renderHook} from "@testing-library/react-hooks"; 14 15 // define props of bar chart 16 const barChartProps: IBarChartProps = { 17 showLoading: true, 18 chartHeight: 300, 19 itemData: mxData, 20 sortByReject: true, 21 }; 22 23 /** 24 * 25 * @param height height of chart 26 * @param sortByReject true means sort by total reject. false means sort by label 27 * @returns render component with given property 28 */ 29 const renderFrame = (height?: number, sortByReject = false, chartData?: IChartDataList, showLoading = false) => ( 30 <BarChart 31 showLoading={showLoading ? showLoading : false} 32 chartHeight={height ? height : 320} 33 itemData={chartData ? chartData : hotData} 34 sortByReject={sortByReject} 35 /> 36 ); 37 38 describe("render bar chart", () => { 39 it("bar height depend on props 'chartHeight'", () => { 40 const chartHeight = 200; 41 const wrappers = create(renderFrame(chartHeight, false)); 42 expect(wrappers.root.findByType(ItemWithLoading).props.height).toBe(chartHeight); 43 wrappers.unmount(); 44 }); 45 46 it("bar showLoading depend on props 'showLoading'", () => { 47 // render component with property 'showLoading' as true 48 const wrapper = create(renderFrame(undefined, undefined, undefined, true)); 49 expect(wrapper.root.findByType(ItemWithLoading).props.showLoading).toBeTruthy(); 50 wrapper.unmount(); 51 }); 52 53 it("when props data is empty", () => { 54 const emptyData: IChartDataList = { 55 titleKey: "empty list", 56 detectionArr: [], 57 chartData: {labels: [], datasets: []}, 58 }; 59 // render component with empty data 60 const frame = create(renderFrame(undefined, undefined, emptyData)); 61 expect(frame.root.findByType(ItemWithLoading).props.ele).toBeFalsy(); 62 frame.unmount(); 63 }); 64 65 66 it("when props 'sortByReject' is false, test render hot chart", () => { 67 const newRef = { 68 current: { 69 update: jest.fn(), 70 // hot data has one items 71 data: hotChartData, 72 }, 73 }; 74 jest.spyOn(React, "useRef").mockReturnValueOnce(newRef); 75 /** render hooks 76 * more detail you can see [react-hooks-testing](https://react-hooks-testing-library.com/) 77 */ 78 renderHook(() => BarChart(barChartProps)); 79 expect(newRef.current.update).toHaveBeenCalledTimes(1); 80 }); 81 });