0. jest與其他測試框架的比較
由於筆者剛剛接觸測試,經驗還不是很豐富,所以只能說一些粗淺的認識。經過1周左右的調研,得出一個結論:
jest 約等於 karma + mocha + chai
也就是說,測試、斷言、覆蓋率等,jest一個人就都做了。如果一個東西能解決,而且沒有太大的性能問題的話,爲什麼不選擇它呢?而且他還是facebook的親兒子,用它來做react的單測,聽起來就很配~不過據說airbnb的enzyme寫react的單測,非常的方便,所以我們選擇建立一個jest + enzyme的測試框架。
先附上jest配置時需要解決的問題,方便大家查閱
1、.babelrc裏test環境關掉去掉modules: false
2、設置moduleNameMapper適配webpack.resolve.alias
3、設置transform轉換css等樣式文件
4、注意enzyme與react版本匹配問題
安裝jest也很簡單,通常我們都會用到babel,所以還會需要babel-jest
$ npm i -D jest babel-jest
後面還會安裝enzyme來測試react組件,由於需要匹配react版本,所以後面再細說。
1. jest的語法
import { sum } from '../../src/util';
describe('# sum test', () => {
it('1 + 1 = 2', () => {
expect(sum([1, 2])).toBe(3);
});
it('2 + 2 = 4', () => {
expect(sum([2, 2])).toBe(4);
});
});
不用多說什麼了,跟其他框架沒有什麼太大區別,import要測試的模塊,describe分塊,it分條件,expect運行。具體語法細節參考jest官方文檔。在測試普通js模塊時,基本不會碰到什麼問題,但是在測試react組件時,就會出現各種各樣的配置問題,尤其是與webpack搭配的時候。
2. jest的配置
jest的配置文件爲config目錄下的 jest.config.js(也可以起其他名字或者直接寫在package.json裏),用jest --config config/jest.config.js 來指定運行,先附上配置文件,然後再詳細說明配置時會遇到的問題。
const path = require('path');
module.exports = {
rootDir: path.resolve(__dirname, '../'),
collectCoverage: true, // 是否收集測試時的覆蓋率信息
collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx,mjs}'], // 哪些文件需要收集覆蓋率信息
coverageDirectory: '<rootDir>/test/coverage', // 輸出覆蓋信息文件的目錄
coveragePathIgnorePatterns: ['/node_modules/', '<rootDir>/src/index.jsx'], // 統計覆蓋信息時需要忽略的文件
moduleNameMapper: { // 主要用於與webpack的resolve.alias匹配,注意正則寫法
'^src(.*)$': '<rootDir>/src$1',
'^util(.*)$': '<rootDir>/src/util$1',
'^assets(.*)$': '<rootDir>/src/assets$1',
'^components(.*)$': '<rootDir>/src/components$1',
},
setupFiles: ['<rootDir>/test/setup.js'], // 運行測試前可運行的腳本,比如註冊enzyme的兼容
testMatch: [ // 匹配的測試文件
'<rootDir>/test/**/?(*.)(spec|test).{js,jsx,mjs}',
'<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}',
],
testURL: 'https://test.com?empty=&num=0&str=str&cstr=中文&encode=%e4%b8%ad%e6%96%87', // 運行環境下的url,默認about:blank
transform: {
'^.+\\.(js|jsx|mjs)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(css|less)$': '<rootDir>/test/cssTransform.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/fileTransform.js',
},
transformIgnorePatterns: [ // 轉換時需要忽略的文件
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$',
],
};
另外還要在.babelrc裏面配置一下,請根據自己項目情況調整
{
"presets": [
["env", {"modules": false }],
"react",
"stage-2"
],
"env": {
"test": {
"presets": [["env"], "react", "stage-2"]
}
}
}
主要是在env.test裏面要把modules:false關掉,jest在運行單測時會默認走env.test裏面的配置。
1、webpack的resolve.alias引起的問題
如果運行時出現了類似如下的提示:
$ Cannot find module '@src/util' from 'index.jsx'
基本上就是在webpack的resolve.alias設置了@src的別名,而jest找不到。這個時候就需要配置moduleNameMapper了(見上文),配置的時候要注意正則的語法,還是有點難度的。
2、css等樣式引起的問題
如果運行時出現了類似如下的提示:
$ SyntaxError: Unexpected token .
基本上是因爲解析css等樣式問題出得問題,我們單測的時候其實不會去關心樣式的,所以我們需要把所有的樣式文件重定向一下,返回一個空的模塊,這樣就不會報錯了。
這個空文件可以寫成下面這樣,是從create-react-app裏借鑑過來的,然後配置transform屬性(見上文)即可,注意文件路徑,請根據自己的項目進行修改。新建test/cssTransform.js
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};
除了樣式,我們還需要忽略圖片、字體等文件,就需要新建test/fileTransform.js文件。
const path = require('path');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
},
};
3、window.location相關的問題
在測試中,我們的被測試代碼有可能會訪問window.location等url相關的參數,而node環境裏是沒有url的,這個時候就需要配置一下testURL屬性(見上文),來給jest(實際是jsdom)一個默認的url屬性,讓測試文件去訪問。這個配置暫時能夠解決一些簡單的問題,下面的文章會用到,但是目前還沒有發現能夠在單測文件裏動態修改url的辦法,請大牛多多指教。
3. enzyme的配置
需要根據項目的react版本來安裝對應的enzyme,詳見文檔,下面以[email protected]爲例,需要安裝。react@16就不需要安裝這麼多,所以要根據安裝時候的提示進行針對性的安裝。
$ npm i -D enzyme enzyme-adapter-react-15 [email protected] [email protected]
然後需要對enzyme進行一下配置
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
Enzyme.configure({ adapter: new Adapter() });
可以把這段代碼保存爲一個文件,如test/setup.js,然後在jest的配置文件裏設置setupFiles屬性(見上文),這樣就不需要在每個react組件的測試文件裏都重複寫這段代碼了。
用enzyme寫測試,就像寫jquery一樣,對於前端同學應該沒什麼難度。只要把組件包裹在shallow、mount或render裏面即可。至於這三者的區別,可詳見官方文檔,下面是個demo,還是比較容易看懂的。另外,我們會另起一篇來專門介紹如何寫組件的用例。
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import AdjustRule from '../index';
describe('# Component AdjustRule', () => {
it('should render without throwing an error', () => {
expect(shallow(<AdjustRule />).contains(<p className="ajrl-title"></p>)).toBe(true);
});
it('should mount in a full DOM', () => {
expect(mount(<AdjustRule />).find('.ajrl-title').length).toBe(1);
});
it('should render to static HTML', () => {
expect(render(<AdjustRule />).find('.ajrl-title').text()).toEqual('test');
});
});
4. 結束
實踐才能出真知,請看本系列的下一篇文章來上手實踐。不過你得首先有個react的開發環境,可參考《從零搭建前端開發環境》系列來搭建一個自己熟悉的react開發環境。本文的配置均基於如下目錄結構,請根據自身情況調整,注意領會精神。
demo
|- config/
|- jest.config.js
|- webpack.base.js
|- webpack.dev.js
|- webpack.prod.js
|- src/
|- assets/
|- logo.jpg
|- components/
|- HelloWorld/
|- __tests__/
|- index.spec.js
|- index.jsx
|- index.less
|- util/
|- index.js
|- index.jsx
|- index.less
|- test/
|- spec/
|- util.spec.js
|- .eslintrc.js
|- cssTransform.js
|- fileTransform.js
|- setup.js
|- .babelrc
|- .eslintignore
|- .eslintrc.js
|- .gitignore
|- .postcssrc.js
|- index.html
|- package.json
擴展閱讀: