如何在mocha中測試TypeScript文件
mocha是我比較喜歡的一款的單元測試框架。使用mocha直接測試TypeScript文件,需要結合babel,preset-env,preset-typescript以及babel-register。
// linked-list.ts
export default class LinkedList(){
}
// ./test/linked-list.js
require('@babel/register')({
presets: [
['@babel/preset-env', { modules: 'commonjs'}],
['@babel/preset-typescript']
],
extensions: ['.ts']
})
const LinkedList = require('./src/linked-list.ts')
describe('#test', function(){
it('#1', function(){
let linkedList = new LinkedList()
...
})
})
需要注意的是
- babel-register 會將參數中的配置和bablerc中的配置共同起作用
- preset-typescript 不使用tsconfig.json中的編譯選項
- 使用preset-typescript的時候,大多數的編譯選項可以通過babel來實現
然後在命令行中執行
mocha ./test/linked-list.js
發現報如下錯誤
LinkedList is not a constructor
排查之後發現,在測試文件./test/linked-list.js 中
const LinkedList = require('./src/linked-list.ts')
導入的LinkedList真實為
{ default: function LinkedList(){}, __esModule: true }
所以我應該在測試文件中加上
const LinkedList = require('./src/linked-list.ts').default
才能正確執行。
但是上面的寫法很麻煩,而且很蠢。很顯然babel將我的ts文件從ES module 轉換為 commonjs的時候,是將export default 的內容掛載module.exports.default上面,而不是我一開始期望的export default === module.exports =
babel v6之後ES module TO commonjs
在babel v6之後,export default 導出的內容不再使用module.exports = 導出。
// es6.js
export default function sum(){}
// commonjs
exports.__module = true
exports['default'] = function sum(){}
exports.__module用來告訴打包工具(基本上這是所有打包工具的事實標準)當前模塊是從ES module 轉換過來的,完全兼容ES module。所以當使用import 導入這樣一個commonjs模塊的時候,應該使用__importDefault helper來加載
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
}
exports.__esModule = true;
var bar_1 = __importDefault(require("bar"));
這也是我們前面必須加上.default的原因。
但是這樣子很麻煩,而且很蠢。如果我們還想保持es5 之前的模塊交互邏輯,也就是export default導出的內容使用commonjs module.exports = 默認導出。我們可以使用babel-plugin-add-module-exports。
require('@babel/register')({
presets: [
['@babel/preset-env', { modules: 'commonjs'}],
['@babel/preset-typescript']
],
plugins: ['add-module-exports'],
extensions: ['.ts']
})
該插件會在當你的模塊只有export default 默認導出的時候,轉換為commonjs的默認導出。
// es6.js
export default function sum(){}
// commonjs
exports.__module = true
exports['default'] = function sum(){}
modules.exports = exports['default']
這樣子我們就不需要加上麻煩的.default了。
是否使用默認導出
對於是否使用默認導出(export default / module.exports = ),好像越來越多的人開始持否定態度。就連javascript 的創造者Nicholas C. Zakas也表示不會再使用默認導出了。要知道npm上大多數的包或者模塊都是使用module.exports默認導出的。
那麼默認導出到底有什麼弊端呢?根據尼古拉斯的説法,默認導出最重要的一個弊端是會悄無聲息地轉移到其他變量,無法通過搜索代碼的方式來跟蹤。這給代碼閲讀,排查錯誤造成了很大的干擾。導出的最佳實踐應該是**只使用具名導出
舉個例子
// linked-list.js
export default class LinkedList{}
// a.js
import list from './linked-list'
在一開始,我們知道list就是LinkedList類。但是隨着項目的迭代,我們可能在多處使用了這個LinkedList類,但是我們都想a.js一樣改變了這個類名,當我們想通過LinkedList全局搜索何處引用的時候,我們就已經無法得到正確的結果了。每次閲讀代碼的時候,我們都需要拉到頭部看下這個list是從何處導入的,導入的是什麼,這會嚴重中斷我們的開發進程。
PS: export default 也是一種具名導出(named export),只不過這個名字是default.
// a.js
export default function LinkedList(){}
// b.js
import { default } from './a.js'
import LinkedList from './a.js'
// default === LinkedList