博客 / 詳情

返回

使用mocha測試TypeScript文件

如何在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 
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.