AngularJS + Jest 實戰

  1. AngularJS + Jest 實戰
    1. 測試案例
    2. 測試初始畫面
      1. 測試目標
      2. 建立測試程式
      3. 寫測試
    3. 可能的問題: Jest 的 loader
    4. 測試互動改畫面
      1. 修改畫面
      2. 寫測試
    5. 測試 service 傳給 component 的值
      1. 改 component
      2. 寫測試
    6. 結論

AngularJS + Jest 實戰

實做專案: https://github.com/dwatow/AngularJS-demo/tree/jest-demo

一開始,先 git clone repository

git@github.com:dwatow/AngularJS-demo.git

測試案例

  1. 顯示變數內容到畫面
  2. 使用 button 改變變數,同時修改畫面

測試初始畫面

有兩個東西可以測

  1. binding 進 component 的值
  2. $http 透過 API 得到的值

binding 進來的值較單純,$http 等到測非同步時再來說。

測試目標

component 在註冊時,binding 是這樣寫的

HelloWorld/index.html

1
2
3
<div class="hello">
<h1>{{ vm.msg }}</h1>
...

HelloWorld/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class controller {
constructor() {
// ...
}
}

export default angular.module("helloWorld", []).component("helloWorld", {
template,
controller,
bindings: {
msg: "<",
},
controllerAs: "vm",
}).name;

在 App 的使用 helloWorld 方式是這樣

App/index.html

<hello-world msg="'Welcome to Your AngularJS App'"></hello-world>

建立測試程式

為了簡化 AngularJS 框架初始在測試程式的程式碼,使用 angularjs-jest [1]

宣告 AngularJS 和 AngularJS 的 mock 函數[2]

test/helloWorld.spec.js

1
2
import angular from 'angular';
import 'angular-mocks';

引入要用測試的 module

3
4
import mathservice from '../src/service/mathmodule.js';
import helloWorld from '../src/components/HelloWorld/index.js';

引入要用測試的 angularjs-jest

5
import { createTestApp } from 'angularjs-jest';

引入能在 node 跑類似 jQuery 的功能

6
import cheerio from 'cheerio';

用 angularjs-jest 初始化。並且將 $componentController inject 進測試 app 中。它可以讓我們得到 component 的實體。(不包含 template)

7
8
9
10
11
12
describe('component: HelloWorld', function () {
const getTestApp = () => createTestApp({
modules: [helloWorld, mathservice],
access: ['$componentController'],
});
//...

寫測試

13
14
15
//...
it('initial render binding msg: should be Welcome to Your AngularJS App', () => {
const app = getTestApp();

直接取得渲染結果,並且將結果載入 cheerio 中。
並且測試 h1 內的文字是否與 binding 的字是一樣的。

16
17
18
const element = app.render(`<hello-world msg="'Welcome to Your AngularJS App'"></hello-world>`)
const $ = cheerio.load(element.html())
expect($('h1').text()).toBe("Welcome to Your AngularJS App");

取得 controller 程式執行。並且同時設定 binding。
測試 vm.msg 內的文字是否與 binding 的字是一樣的。

19
20
21
22
23
  const vm = app.$componentController(helloWorld, null, {
msg: "Welcome to Your AngularJS App",
});
expect(vm.msg).toEqual("Welcome to Your AngularJS App");
});

可能的問題: Jest 的 loader

因為 Jest 只吃 JavaScript (和 Webpack 一樣) 所以,有時可以拿 webpack 的 loader 來使用。在此打算使用 html-loader 來載入 html 檔。

安裝 html-loader-jest 較方便

npm install -D html-loader-jest

在 package.json 設定 jest 區段
(這是可以設定 Jest 的眾多方法其中之一)

package.json

1
2
3
4
5
6
7
8
9
10
{
//...
"jest": {
// ...
"transform": {
"^.+\\.(js|jsx)$": "babel-jest",
"^.+\\.html?$": "html-loader-jest",
// ...
}
}

這樣就可以將載入的 html 檔轉成 JavaScript 的字串。

測試互動改畫面

修改畫面

目前的測試程式是仿照 vue-cli 產生的畫面。它滿足了第一個測試案例,將變數顯示到畫面上。而第二個測試案例「使用 input」必須要先修改一下,才可以

HelloWorld/index.html

<div class="hello">
<h1>{{ vm.msg }}</h1>
<p>
<input type="button" value="change head 1"
ng-click="vm.changeHead('Chris')">
</p>

HelloWorld/index.js

class controller {
//...
changeHead(msg) {
this.msg = msg;
}
}

寫測試

先將初始字串印出來。

test/helloWorld.spec.js

1
2
3
4
5
it('hello world click button change text to Chris', () => {
const vm = app.$componentController(helloWorld, null, {
msg: "Welcome to Your AngularJS App",
});
expect(vm.msg).toBe("Welcome to Your AngularJS App");

再執行 changeHead()ng-click 綁定的 function
再測試一次一樣的位置,顯示的內容要改變

6
7
8
  vm.changeHead('Chris')
expect(vm.msg).toBe("Chris");
});

要自行執行 event callback[3]

測試 service 傳給 component 的值

這一部份就是純邏輯的測試,非常適合 unit test

改 component

HelloWorld/index.js

加上一個

1
2
3
4
5
6
7
8
9
10
11
class controller {
constructor($log, mathservice) {
this.$log = $log;
this.mathservice = mathservice;
}

$onInit() {
this.math = this.mathservice.addTwoNumbers(1, 2);
this.$log.log(this.math);
}
}

寫測試

在取得 componentController 之後,

test/helloWorld.spec.js

1
2
3
4
5
it('initial render service data', () => {
const vm = app.$componentController(helloWorld);
vm.$onInit();
expect(vm.math).toBe(3);
});

要自行執行 lifecycle

結論

使用 Jest 測試 AngularJS

  • 沒有一個好的方式可以觸發 event 。
    只能直接操作 component 的 event callback 假裝操作了 event。
  • 不能自動執行 component 的 lifecycle [4][5]

angularjs-jest 的 render 功能

  • 無法再操作 dom 進行 event 的 trigger。會出現 trigger is not a function 的錯誤。也許是因為執行在 node.js 上面。
    不過對於 snapshot 測試初始渲染的來說是一件非常方便的事。因為它會自動執行 $onInit 的結果。

下次再來試,怎麼測試 API 傳送的 case


  1. AngularJS and Jest. Three steps to improve your legacy frontend tests, 1. Throw away the cluttered test setup boilerplate ↩︎

  2. ngMock - AngularJS ↩︎

  3. Unit-testing Component Controllers ↩︎

  4. Angular’s $componentController ↩︎

  5. PCHR-3944: Update to AngularJS v1.7 #2790, Use of $onInit hook in components ↩︎