为 ionic 3.x(ts)项目配置单元测试框架

这是一个配置教程,关于 karma 和 jasmine 的知识请参考 karma docjasmine doc

开始之前先看一下我们的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
├── angular-cli.json
├── config.xml
├── ionic.config.json
├── node_modules
├── package.json
├── resources
├── src
├── tests
├── tsconfig.json
├── tslint.json
├── typings
├── typings.json
└── www

这是一个直接使用 ionic-cli 创建的 ionic3.x 项目

step1

在项目的 package.jsondevDependencies 中添加如下内容,然后执行 npm i:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"@angular/cli": "^1.3.2",
"@ionic/app-scripts": "2.1.3",
"@types/jasmine": "^2.5.54",
"@types/node": "^8.0.26",
"codelyzer": "^3.1.2",
"jasmine": "^2.8.0",
"jasmine-core": "^2.8.0",
"jasmine-spec-reporter": "^4.2.1",
"karma": "^1.7.1",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-coverage-istanbul-reporter ": "^1.3.0",
"karma-jasmine": "^1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-mocha-reporter": "^2.2.4",
"karma-remap-istanbul": "^0.6.0",

typings.json (如果没有就创建一个) 文件中添加以下内容,然后执行 typings install

1
2
3
4
5
{
"globalDependencies": {
"jasmine": "registry:dt/jasmine#2.5.2+20170317130948"
}
}

angular-cli.json(如果没有就创建一个)文件中添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"project": {
"version": "1.0.0",
"name": "app name"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets"
],
"index": "index.html",
"main": "./app/main.ts",
"polyfills": "../tests/polyfills.ts",
"test": "../tests/test.ts",
"tsconfig": "../tests/tsconfig.spec.json",
"prefix": "app",
"mobile": false,
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "../tests/environments/environment.ts",
"environments": {
"dev": "../tests/environments/environment.ts",
"prod": "../tests/environments/environment.prod.ts"
}
}
],
"addons": [],
"packages": [],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": true,
"module": false,
"pipe": true,
"service": true
}
}
}

step2

在根目录下创建 karma.conf.js 文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-remap-istanbul'),
require('karma-mocha-reporter'),
require('@angular/cli/plugins/karma')
],
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./tests/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
angularCli: {
config: './angular-cli.json',
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['mocha', 'karma-remap-istanbul']
: ['mocha'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

step3

在 tests 目录下面创建 tsconfig.spec.json 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es6",
"dom"
],
"mapRoot": "./",
"module": "es6",
"moduleResolution": "node",
"outDir": "../dist/out-tsc",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"../node_modules/@types"
]
}
}

step4

在 tests 目录下面创建 polyfills.ts 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';

import 'reflect-metadata';
import 'zone.js';

step5

在 tests 目录下面创建 test.ts 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import './polyfills';

import 'zone.js/dist/long-stack-trace-zone';

import 'zone.js/dist/proxy.js';

import 'zone.js/dist/sync-test';

import 'zone.js/dist/jasmine-patch';

import 'zone.js/dist/async-test';

import 'zone.js/dist/fake-async-test';


import {getTestBed, TestBed} from '@angular/core/testing';

import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';


// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.

declare let __karma__: any;

declare let require: any;


// Prevent Karma from running prematurely.

__karma__.loaded = function (): void {

// noop

};


// First, initialize the Angular testing environment.

getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,

platformBrowserDynamicTesting()
);


// Then we find all the tests.

let context: any = require.context('./', true, /\.spec\.ts/);


// And load the modules.

context.keys().map(context);


// Finally, start Karma to run the tests.

__karma__.start();

step6

tests 目录下面创建 environments 目录,在这个目录中创建两个文件,内容分别如下

  1. environment.ts
1
2
3
export const environment: any = {
production: false,
};
  1. environment.prod.ts
1
2
3
export const environment: any = {
production: true,
};

Final step

package.json 中添加一个 scripts

1
"test": "ng test"

至此,karma + jasmine 的测试环境已经配置完成了,执行 npm test 就可以开始执行测试用例了。

测试用例要放到 tests 目录下。我为我项目中的一个管道写的测试用例类似这个样子:

message-date.spec.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import {MessageDatePipe} from "../../src/pipes/message-date/message-date";
import {DATE_UNIT} from "../../src/constants/date-cons";
import moment = require("moment");

describe('test all cases of message date', () => {

let messageDatePipe: MessageDatePipe;
const today = moment();
const oneMinuteAgo = today.clone().add(-1, DATE_UNIT.MINUTES).format('x');
const oneDayAgo = today.clone().add(-1, DATE_UNIT.DAYS).format('x');
const oneWeekAgo = today.clone().add(-7, DATE_UNIT.DAYS).format('x');

beforeEach(() => {
messageDatePipe = new MessageDatePipe();
});

it('one week ago', () => {
expect(messageDatePipe.transform(oneWeekAgo)).toMatch(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
});

it('one day ago', () => {
expect(messageDatePipe.transform(oneDayAgo)).toMatch(/[0-9]{2}-[0-9]{2}/);
});

it('within current day', () => {
expect(messageDatePipe.transform(oneMinuteAgo)).toMatch(/(A|P)M [0-9]{2}:[0-9]{2}/);
});

it('one week ago num', () => {
expect(messageDatePipe.transform(parseInt(oneWeekAgo))).toMatch(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
});

it('one day ago num', () => {
expect(messageDatePipe.transform(parseInt(oneDayAgo))).toMatch(/[0-9]{2}-[0-9]{2}/);
});

it('within current day num', () => {
expect(messageDatePipe.transform(parseInt(oneMinuteAgo))).toMatch(/(A|P)M [0-9]{2}:[0-9]{2}/);
});
});

运行后结果如图所示