本節(jié)內(nèi)容大綱:
- 手動(dòng)模擬
- 手動(dòng)模擬自定義模塊
- 測試第三方模塊lodash
- 手動(dòng)模擬axios
- configuring
- option
- cli
- 從命令行運(yùn)行
- 運(yùn)行
- jest命令支持駝峰和破折號
- reference
手動(dòng)模擬
手動(dòng)模擬用于用模擬數(shù)據(jù)存根功能。例如,您可能不想訪問網(wǎng)站或數(shù)據(jù)庫等遠(yuǎn)程資源,而是創(chuàng)建一個(gè)允許您使用虛假數(shù)據(jù)的手動(dòng)模擬。這可確保您的測試快速而穩(wěn)定。
這里分享三個(gè)手動(dòng)模擬:
- 手動(dòng)模擬自定義模塊
- 測試第三方模塊lodash
- 手動(dòng)模擬axios
附帶規(guī)則(謹(jǐn)記):
__mocks__/手動(dòng)模擬是通過在緊鄰模塊的子目錄中編寫模塊來定義的。例如,要模擬目錄user中調(diào)用的模塊models,創(chuàng)建一個(gè)名為的文件user.js并將其放入models/mocks目錄中。請注意,mocks文件夾區(qū)分大小寫,因此MOCKS在某些系統(tǒng)上命名目錄會中斷。

手動(dòng)模擬自定義模塊
新建文件__tests__/models/user.ts
// eslint-disable-next-line prefer-const
const utils = {
count: 12,
getCount: (num: number) => {
return num * 20
}
}
export default utils;
新建文件__tests__/models/mocks/user.ts
const utils = jest.createMockFromModule<typeof import('../user')>('../user');
utils.default.getCount = jest.fn(x => x*10)
utils.default.count = 30;
export default utils.default;
新建測試__tests__/manual/user.test.tsx
import utils from "./models/user";
// import { default as utils } from './models/user'; // 也可以這樣導(dǎo)出
jest.mock('./models/user')
describe('test manual mock',() => {
test('if original user model', () => {
// 此時(shí)拿到的數(shù)據(jù)是__mocks__/user.ts重新賦值的內(nèi)容
// 如果__mocks__/user.ts 中導(dǎo)出不是utils.default,而是utils,那么這里的utils雖然有值,但是測試這個(gè)文件拿到的utils.default為undefined
console.log(utils,'---')
expect(utils.getCount(10)).toBe(100);
});
})
注意點(diǎn):
- 由于eslint對于沒有改變的數(shù)據(jù)強(qiáng)行使用const,因此需要忽略這條檢查:eslint-disable-next-line
- __mocks__中導(dǎo)入的對象,實(shí)際上數(shù)據(jù)保存在一個(gè)default的屬性上,并且導(dǎo)出也必須是導(dǎo)出這個(gè)defualt。因?yàn)樵跍y試文件中無法通過utils.default進(jìn)行訪問
- createMockFromModule注意他的ts寫法和js寫法的不同,參考文檔:https://www.jestjs.cn/docs/jest-object#jestcreatemockfrommodulemodulename
測試第三方模塊lodash
測試第三方模塊比測試自定義模塊更簡單,我們首先要建立一個(gè)__mocks__文件夾,然后在這個(gè)文件夾里面對第三方模塊進(jìn)行重寫;然后新建測試導(dǎo)入lodash,并且不需要使用jes.mock('lodash')
這里前面已經(jīng)建立了__mocks__,這里只需要在新建文件
__tests__/models/mocks/lodash.ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lodash = jest.createMockFromModule<any>('lodash');
lodash.head = () => 5;
export default lodash;
新建測試__tests__/manual/testLodash.test.tsx
import lodash from 'lodash';
describe('test manual',() => {
test('if lodash head is mocked', () => {
expect(lodash.head([2, 3])).toBe(5);
});
})
注意事項(xiàng):
- mocks文件默認(rèn)建立在node_module相鄰的目錄中,但是我們項(xiàng)目中的jest.config.js配置了root:"/src/",因此mocks只能放在src及以下的子文件夾內(nèi)
- 注意ts的寫法,即需要給lodash返回一個(gè)類型,我這里直接寫了一個(gè)any
手動(dòng)模擬axios
測試不會發(fā)起網(wǎng)絡(luò)請求,如果我們執(zhí)意執(zhí)行的話,會提示我們報(bào)錯(cuò)
console.errorError: Error: connect ECONNREFUSED ::1:80at Object.dispatchError (D:\workFile\react-demo\20230505\jest-learn\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:63:19)
因此我們有兩種方法:一個(gè)是前面聊到的章節(jié)=>模擬異步/模擬axios,如果忘記了可以返回去查看,另一個(gè)方法便是自定義模擬。經(jīng)過前面對自定義模擬的了解,我們知道,如果在一個(gè)ts文件旁邊放一個(gè)__mocks__,并在這個(gè)文件夾里面新建一個(gè)和__mocks__相鄰的同文件名,那么這個(gè)文件將會處于自定義狀態(tài),我們在測試的時(shí)候就只需要jest.mock()指定這個(gè)__mocks__相鄰文件的路徑,就能達(dá)到無需發(fā)起網(wǎng)絡(luò)請求,而自定義返回?cái)?shù)據(jù)的效果。
因此我們新建文件api/__mocks__/home.ts,這樣他就是模擬了api/home.ts文件:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const home_api: any = jest.createMockFromModule<typeof import('../home')>('../home')
home_api.default.getHomeHead = () => {
console.log('---')
return new Promise((resolve) => {
resolve({
time: 1,
title: "news over world"
})
})
}
export default home_api.default;
新建測試__tests__/manual/testAxios.test.tsx
import utils from '../../api/home';
// 一定要有,如果沒有的話,那么就變成了單純的方法引入了
jest.mock('../../api/home');
describe('test manual',() => {
it('test manual axios',async () => {
console.log(utils,'====')
const res = await utils.getHomeHead();
console.log(res,'jjjj')
})
})
如果這樣跑,大家會出現(xiàn)一個(gè)怪異的問題,那就是導(dǎo)入utils竟然包含一個(gè)default,這是因?yàn)槲覀兾覀兾募pi/__mocks__/home.ts是默認(rèn)導(dǎo)出的,因此得改成導(dǎo)出home_api.default,而不是home_api;改完之后還是不行,因?yàn)閔ome.ts和__mocks__/home.ts在行為上必須一致,即前者默認(rèn)導(dǎo)出,那么后者也需要默認(rèn)導(dǎo)出一次,而我們之前home.ts是單獨(dú)導(dǎo)出的,因此需要改一下:
修改api/home.ts
...
export const getHomeData = async () => {...}
export const getHomeHead = async () => {...}
export const request = (url: string) => {...}
export default {
request,
getHomeHead,
getHomeData
}
這樣,我們才能完美的通過測試。
configuring
Jest 的理念是默認(rèn)情況下工作得很好,但有時(shí)您只需要更多的配置能力。他保存在一個(gè).config.js|ts|mjs|cjs|json文件當(dāng)中,可以直接導(dǎo)出一個(gè)對象或者一個(gè)函數(shù)。這里我主要講解一些平時(shí)我們可能會用到的,后續(xù)講解react組件測試的時(shí)候我會再加上配置運(yùn)行的方法,同時(shí)以下每個(gè)屬性的介紹建議過一遍。
export default {
verbose: true,
}
// or
import type {Config} from 'jest';
export default async (): Promise<Config> => {
return {
verbose: true,
};
};
options
jest有很多配置,但是我們并不是需要全部關(guān)心,因?yàn)楣ぷ鳟?dāng)中很少會運(yùn)用到,我們只需要知道一些常用的就行。這里我以我單測遇到的一些配置進(jìn)行講解,當(dāng)看完我這部分的配置之后,相信大家會對單測配置有一個(gè)基本了解,從而讓我們?nèi)タ垂ぷ髦械膉est配置,能夠從容不迫。退一步講,如果還有不明確的,可以查詢官網(wǎng):
https://jestjs.io/docs/configuration#reference
直接上配置(需要注意以下配置,最好是記憶,其余的基本上用不到):
export default {
// 用作配置jest的基礎(chǔ)字段,這里我們?nèi)绻莟s的項(xiàng)目,可以使用ts-jest,但是前提得先安裝他
preset: 'ts-jest',
// 可以參考“從命令行運(yùn)行”內(nèi)容,用于搜索文件目錄路徑的跟頁路徑,一般設(shè)置成如下,這樣我們
roots: ['<rootDir>/src/'],
// verbose用于是否開啟報(bào)告每個(gè)單測結(jié)果,默認(rèn)是開啟一個(gè)線池,可以參考“參考reference”的對比圖片
verbose: false,
// jest 默認(rèn)測試環(huán)境是node,如果是創(chuàng)建的web應(yīng)用,那么就使用jsdom
testEnvironment: 'jsdom',
// 輸出覆蓋范圍的文件目錄,即生成覆蓋率的文件夾名稱
coverageDirectory: 'coverage',
// 每次測試前自動(dòng)清除模擬調(diào)用、實(shí)例和結(jié)果
clearMocks: true,
// 指示應(yīng)使用哪個(gè)提供程序來檢測覆蓋范圍的代碼
coverageProvider: "v8",
// 從正則表達(dá)式到模塊名稱或到允許存根資源的模塊名稱數(shù)組的映射
// 我們換一種方式去理解:就是供我們配置測試路徑的映射和解析資源的映射,資源包括css和img等,其中圖片和css等資源我們可以,此外
// 一些第三方的使用如果test無法通過,也可以在這里配置去忽略他
// 安裝identity-obj-proxy,以下為寫法參考,或者你可以參考“測試React組件”這一節(jié)內(nèi)容
// 這里面的$1表示正則里面匹配組的序號
moduleNameMapper: {
// 處理css等資源
'\\.(css|scss|less)': 'identity-obj-proxy',
// 處理圖片等資源
'\\.(jpg|png|webp|gig|svg|mp4|webm|mp3|m4a|aac)$': 'identity-obj-proxy',
// 配置jest測試環(huán)境,訪問組件的通配符,但是如果只是訪問ts文件,那么則無需要配置,只需要配置vite和tsconfig的配置即可
'^~/(.*)': '<rootDir>/src/$1'
},
// 收集需要測試的 文件,支持匹配正則
collectCoverageFrom:[],
// 收集需要測試的文件有哪些,支持匹配正則
testMatch:[]
}
cli
命令行運(yùn)行器jest有許多有用的選項(xiàng)。您可以運(yùn)行jest --help以查看所有可用選項(xiàng)。下面顯示的許多選項(xiàng)也可以一起使用,以完全按照您想要的方式運(yùn)行測試。Jest 的每個(gè)配置選項(xiàng)也可以通過 CLI 指定。說人話,就是為了看懂別人的寫法,因?yàn)樽约汉芏鄷r(shí)候直接就jest了。。。
從命令行運(yùn)行
查看: jest --help
運(yùn)行
- jest 直接運(yùn)行
- jest 路徑名 => 運(yùn)行的路徑是基于jest.config.js中的roots
例如:
roots: [
"<rootDir>/src/_test/common"
],
那么我們執(zhí)行jest的時(shí)候執(zhí)行的是src/_test/common下面的test內(nèi)容,同時(shí)我們在運(yùn)行的時(shí)候執(zhí)行jest common/路徑名就相當(dāng)于是匹配到了/src/_test/common/路徑名
運(yùn)行與更改文件相關(guān)的測試: jest -o
他運(yùn)行的測試文件是運(yùn)行您本地沒有commit的jest文件,如果commit之后,jest -o執(zhí)行無效,并且會提示我們直接執(zhí)行jest或者執(zhí)行jest --all
jest命令支持駝峰和破折號
這就說明下面兩個(gè)效果是一樣,都表明更新快照,這個(gè)在測試react組件,組件內(nèi)容發(fā)生改變的時(shí)候使用
jest --update-snapshot
jest --updateSnapshot
參考reference
jest的命令行也有很多,但是絕大部分我們可能并不需要去關(guān)注,這里我以我自己常用的一些作為講解,如果工作中大家遇到了其它的配置,可以參考官網(wǎng)進(jìn)行查閱:
https://jestjs.io/docs/cli
// 打印jest配置信息并運(yùn)行,他同jest --showConfig有相同點(diǎn),不同的是debug還能繼續(xù)測試,而showConfig不會進(jìn)行測試
jest --debug
// 依次執(zhí)行test文件,默認(rèn)是創(chuàng)建一個(gè)測試的工作池
jest --runInBand 等同于jest -i
// 忽略測試文件中的打印信息,這個(gè)只需要了解,因?yàn)闇y試中的打印我們還是需要的
jest --silent => jest -i --silent 實(shí)現(xiàn)單個(gè)測試+忽略測試文件打印
// 重新記錄在此測試運(yùn)行期間失敗的每個(gè)快照
jest --updateSnapshot 等同于jest -u
// 打印jest版本
jest --version
// 按照單個(gè)文件測試結(jié)果,輸出打印,對于調(diào)試比較有用,因?yàn)槟憧梢园l(fā)現(xiàn)是那塊出了問題
// 下面是jest和jest --verbose不同輸出打印的截圖
jest --verbose

// 打印jest的配置信息并推出
jest --showConfig
// 從指定路徑中找到的一個(gè)或多個(gè)項(xiàng)目運(yùn)行測試
// 但是這個(gè)貌似不太好用,因?yàn)楫?dāng)我們加上了projects之后,項(xiàng)目中配置的路徑別名就無法正確找到外部文件的引入了
jest --projects '路徑地址'
因此我們?nèi)粘9ぷ髦?,可能會這樣配置
{
...
"scripts": {
"test-dev": "jest",
"test": "test --ci --runInBand --silent",
"coverage": "jest --coverage --silent"
}
}
看都看到這里了,幫幫忙點(diǎn)贊支持一波~