在前端專案裡,我們通常會使用 Webpac 搭配 Babel Loader
來編譯瀏覽器不相容的語法,Webpac 預設是可以辨識 ESModule 的,而且 tree shaking 功能可以幫我們排除沒有實際使用的 module,所以我們把 compile import
、 export
語法的工作交給 Webpac。因此我們在 babelrc.js
中設定 {modules: false}
,讓 @babel/preset-env
不要 compile modules:
babelrc.js
module.exports = {
presets: [
['@babel/preset-env', {modules: false}],
'@babel/preset-react',
[
...
這時候,我們為專案加入測試,假設有一個 __tests__/utils.js
檔案,要測試 getFormattedValue
這個 function:
tests/utils.js
import {getFormattedValue} from '../utils'
test('formats the value', () => {
expect(getFormattedValue('1234.0')).toBe('1,234.0')
})
現在 npm run test
會拋出錯誤: syntax error -- Unexpected Token {.
。這個錯誤是因為 Jest 是在 node 環境執行的,但 node 並不認得 import
語法。
另一部分,如果我們的專案中已有 babel 配置,Jest 預設會自動抓取我們的 .babelrc
**設定,跑測試時自動使用這份配置。**但因為我們要讓 Webpac 來處理 module,關閉了讓 @babel/preset-env
compile modules 的功能,這就是這個錯誤被拋出的原因。
如果想要繼續讓 Webpac 處理 modules,兼顧 Webpac 的 tree shaking 功能,又同時能讓 Jest 順利跑測試,我們可以透過環境變數來判斷:測試環境時 babel compile modules,其他情況則不 compile。Jest 測試時會自動爲我們設置一個環境變數 NODE_ENV =test
,因此我們可以在 babelrc.js
裡加上:
babelrc.js
const isTest = String(process.env.NODE_ENV) === 'test'
module.exports = {
presets: [
['@babel/preset-env', {modules: isTest ? 'commonjs' : false}],
'@babel/preset-react',
[
...
這個 babel 配置代表:跑測試時,babel 幫我們 compile modules 為 commonjs,確保在 node 環境可以運行,其他時候,我們一樣把 modules 交給 Webpac 來處理。
Tips:Jest 會自動抓取
.babelrc
設定。
我們都知道,在 Node 環境裡並沒有 window
跟 document
object,但我們現在直接在測試檔案中 console.log(window)
然後 npm run test
看看,結果沒有拋出錯誤。這是因為 Jest 預設內建了 jsdom
,透過 jsdom
模擬 browser 環境的物件,所以即使沒有這些 object 的 Node 環境,Jest 依然能順利測試 Browser code。
Jest 可以讓我們配置專案要測試的是在哪個環境運行的程式碼,可以在 CLI 加上 --env tag。假如我們要測試的 code 是 node 環境的程式碼:
npm run test -- --env=node
這時候 Jest 會排除 jsdom
執行測試,剛剛加上的 console.log(window)
就會拋出 ReferenceError: window is not defined
錯誤。
如果不想每次都在 CLI 加上 config,也可以在根目錄新增一個 jest.config.js
配置檔,設置 testEnvironment
為 'jest-environment-node'
:
jest.config.js
module.exports = {
testEnvironment: 'jest-environment-node'
}
假設是前端專案,會包含很多 Browser code,可以改回 'jest-environment-jsdom'
:
jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom'
}
其實, jest-environment-jsdom
跟 jest-environment-node
實際上是 node module,我們可以在 node_modules
資料夾裡找到它們。它們是 Jest 的內建依賴套件,在安裝 Jest 的時候,也會一併安裝。
Tips:Jest 預設已內建 jsdom,透過 jsdom,Jest 可以模擬 browser 環境執行測試
假設我們現在要測試前端 React 專案的一個 component: AutoScalingText
,這個 component 裡 import 了 css module:
auto-scaling-text.js
...
import styles from './auto-scaling-text.module.css'
class AutoScalingText extends React.Component {
...
}
export default AutoScalingText
這時候執行測試,可能會在 import styles from './auto-scaling-text.module.css
這裡拋出 syntax error
。原因是因為:Jest 試圖將這個檔案當作 common JS module 來 require,但實際上並不是,這是一個 css 檔案。
Jest 建議我們可以使用 moduleNameMapper
將原本要引入的檔案,map 到另一個 mock module,Jest 會引入這個 mock。
現在在 jest.config.js
新增一個 moduleNameMapper
object,定義所有符合條件的檔名(在這裡就是所有 CSS 檔),都去拿另一個 module 取代:
jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'\\.css$': require.resolve('./test/style-mock.js'),
},
}
上面代表我們拿 './test/style-mock.js'
這個 module 取代原本的 css file,因此我們需要有一個 test/style-mock.js
檔案,裡面 export 出 style 的 mock:
test/style-mock.js
module.exports = {}
現在,Jest 跑測試時,import styles from './auto-scaling-text.module.css
拿到的 style 會是一個空的 object {}
。
通常在 Webpac 包裹專案時,之所以 import css files 能正常執行,是因為我們在 Webpac 配置了
CSS-loader
跟style-loader
幫忙處理引入 css 檔案。但在測試時,我們也需要讓 Jest 知道如何 import 這些 css 檔案。