web学习笔记03-angular 单元测试简单应用

angular单元测试

1.创建项目

创建项目

2.创建文件夹

在项目中创建3个文件夹分别用于存放项目中用到的html、js、test文件。

3.安装框架

服务器依赖于nodejs,需要安装nodejs的包,首先在根目录下创建package.json文件。

  • 安装angular等框架
1
2
3
4
5
npm install bootstrap -save

npm install install angular -save

npm install angular-mocks -save
  • 安装http-server模块

    1
    npm install http-server -save
  • 安装其他模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    npm install jasmine-core -save

    npm install karma -save

    npm install karma-chrome-launcher -save

    npm install karma-jasmine -save

    npm install karma-junit-reporter -save

    npm install protractor -save

4.启动服务器

要启动node服务器需要在package.json中配置script节点,dependencies中定义依赖包,在script配置start节点用于启动服务器,test节点的内容会在后面讲解。

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

{
"name": "application-name",
"description": "AngularUnitTest",
"version": "0.0.1",
"dependencies": {
"angular": "^1.5.8",
"angular-mocks": "^1.5.8",
"bootstrap": "^3.3.7",
"http-server": "^0.9.0",
"install": "^0.8.1",
"jasmine-core": "^2.5.2",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-jasmine": "^1.0.2",
"karma-junit-reporter": "^1.1.0",
"protractor": "^4.0.8"
},
"scripts": {
"prestart": "npm install",
"start": "http-server -a localhost -p 8000 -c-1",
"pretest": "npm install",
"test": "karma start karma.conf.js",
"test-single-run": "karma start karma.conf.js --single-run"
}
}

配置后运行命令,启动服务器,浏览器上输入http://localhost:8000

npm start

npm start

5.编写功能代码

在文件js中新建js文件index.js。在index.js中定义congroller,实现简单累加方法add,代码如下:

var appControllers = angular.module('app', []);
appControllers.controller('indexCtrl',['$scope',function($scope) {
    $scope.add = function (a, b) {
        if(a && b)
            return Number(a) + Number(b)
        return 0;
    }; 
    $scope.detect = function (a, b) {
        if(a && b)
            return Number(a) - Number(b)
        return 0;
    };       
    $scope.pr = function () {
        console.log('Function pr');
    };
}]);

    在文件html中新建html文件index.html,加入两个输入框用户获取输入,当输入后绑定controller中的add方法实现计算器功能,代码如下:

<p>&lt;!DOCTYPE html&gt;<br /> 
&lt;html lang=&quot;en&quot; ng-app=&quot;app&quot;&gt;<br /> 
&lt;head&gt;<br /> 
    &lt;meta charset=&quot;UTF-8&quot;&gt;<br /> 
    &lt;title&gt;index&lt;/title&gt;<br /> 
&lt;/head&gt;<br /> 
&lt;body&gt;<br /> 
&lt;div ng-controller=&quot;indexCtrl&quot;&gt;<br /> 
    &lt;input type=&quot;text&quot; ng-model=&quot;a&quot; value=&quot;     0&quot;&gt;<br /> 
    +<br /> 
    &lt;input type=&quot;text&quot; ng-model=&quot;b&quot; value=&quot;    0&quot;&gt;<br /> 
    =&lt;span id=&quot;result&quot;&gt;{{add(a,b)}}&lt;/span&gt;<br /> 
&lt;/div&gt;<br /> 
&lt;/body&gt;<br /> 
&lt;/html&gt;<br /> 
&lt;script src=&quot;/node<em>modules/angular/angular.min.js&quot;&gt;&lt;/     script&gt;<br /> 
&lt;script src=&quot;/node</em>modules/angular-mocks/angular-    mocks.js&quot;&gt;&lt;/script&gt;<br /> 
&lt;script src=&quot;/js/index.js&quot;&gt;&lt;/script&gt;</p><br /> 

启动服务器看到下图效果
效果图

6.编写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'use strict';
describe('app', function () {
beforeEach(module('app'));
describe('indexCtrl', function () {
it('add 测试', inject(function ($controller) {
var $scope = {};
//spec body
var indexCtrl = $controller('indexCtrl', {$scope: $scope});
expect(indexCtrl).toBeDefined();
expect($scope.add(2, 3)).toEqual(5);
}));
it('test detect function', function () {
expect(scope.detect(4,3)).toEqual(1);
});
it('test pr function', function () {
expect(scope.pr()).toEqual();
});

})

7.单元测试配置

1
karma init

karma init

在karma配置文件代码中每个节点都有默认注释请参

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

// Karma configuration
// Generated on Mon Sep 19 2016 10:51:55 GMT+0800 (CST)

module.exports = function(config) {
config.set({

// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: './',


// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],


// list of files / patterns to load in the browser
files: [
'node_modules/angular/angular.min.js',
'node_modules/angular-mocks/angular-mocks.js',
'js/index.js',
'test/index-test.js'
],


// list of files to exclude
exclude: [
],


// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},


// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],


// web server port
port: 9876,


// enable / disable colors in the output (reporters and logs)
colors: true,


// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,


// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,


// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],


// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,

// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
plugins: [
'karma-chrome-launcher',
'karma-jasmine',
'karma-junit-reporter'
],

junitReporter: {
outputFile: '/test_out/unit.xml',
suite: 'unit'
}
})
}

在package.json scripts 配置测试信息,指定karma文件地址

"test": "karma start karma.conf.js",

8.运行单元测试

npm test

npm run

test page1

test page2

test result

9.添加网络测试

  • $http service示例
1
2
3
4
5
6
7
8
var app = angular.module('Application', []
)
app.controller('MainCtrl', function($scope, $http) {
$http.get('Users/users.json').success(function(data){
$scope.users = data;
});
$scope.text = 'Hello World!'
});
  • 使用$httpBackend设置伪后台
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

describe('MainCtrl', function() {
//我们会在测试中使用这个scope
var scope, $httpBackend;

//模拟我们的Application模块并注入我们自己的依赖
beforeEach(angular.mock.module('Application'));

//模拟Controller,并且包含 $rootScope 和 $controller
beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_) {
//设置$httpBackend冲刷$http请求
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'Users/users.json').respond([{
id: 1,
name: 'Bob'
}, {
id: 2,
name: 'Jane'
}]);
//创建一个空的 scope
scope = $rootScope.$new();

//声明 Controller并且注入已创建的空的 scope
$controller('MainCtrl', {
$scope: scope
});
}));

// 测试从这里开始
it('should have variable text = "Hello World!"', function() {
expect(scope.text).toBe('Hello World!');
});
it('should fetch list of users', function() {
$httpBackend.flush();
expect(scope.users.length).toBe(2);
expect(scope.users[0].name).toBe('Bob');
//输出结果以方便查看
for(var i=0;i<scope.users.length;i++){
console.log(scope.users[i].name);
}
});
});

以上示例中,可以使用$httpBackend.when和$httpBackend.expect提前设置请求的伪数据。最后在请求后执行$httpBackend.flush就会立即执行完成http请求。

在demo中具体情况是这样的,添加常规常量和变量测试,以及两个网络测试,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//常规变量
$scope.aaa = 1;
$scope.testText = 'Hello Jsamine And Karma';

//
$http.get('users.json').success(function(data){
$scope.users = data;
}).error(function (error) {
$scope.users = error;
});

//获取网络数据,制造伪后台
$http.post('api/000').success(function(data){
$scope.userInfo = data;
}).error(function (error) {
$scope.userInfo = error;
});

在测试文件中这么写

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* Created by apple on 16/9/19.
*/
'use strict';
describe('app', function () {
beforeEach(module('app'));

var scope,ctrl,$httpBackend;

beforeEach(inject(function ($controller, $rootScope,_$httpBackend_) {

$httpBackend = _$httpBackend_;


$httpBackend.when('GET', 'users.json').respond([
{
"id": 1,
"name": "Bob",
"age":20
},
{
"id": 2,
"name": "Jane",
"age":21
},
{
"id": 3,
"name": "gary",
"age":22
}
]);

$httpBackend.when('POST', 'api/000').respond({
"dataList": [
{
"moduleId": "501",
"moduleList": [
{
"moduleId": "501001",
"moduleName": "融资申请",
"moduleUrl": "/financing",
"parentModuleId": "501"
},
{
"moduleId": "501002",
"moduleName": "融资进度查询",
"moduleUrl": "/myFinancing",
"parentModuleId": "501"
}
],
"moduleName": "票据融资",
"moduleUrl": "",
"parentModuleId": "00"
}
],
"imgCaptchaRequired": "N",
"isModifyPwd": "N",
"isSetTradePwd": "N",
"loginId": "15250964261",
"loginType": "00",
"participantName": "guyu",
"phone": "15250964261",
"retCode": "000000",
"retMsg": "交易成功",
"shortName": "",
"totalCount": 3,
"userName": "15250964261"
});

//模拟生成scope, $rootScope是angular中的顶级scope,angular中每个controller中的scope都是rootScope new出来的
scope = $rootScope.$new();

//模拟生成controller 并把先前生成的scope传入以方便测试
ctrl = $controller('indexCtrl', {$scope: scope});


}));


describe('indexCtrl', function () {
it('test add function', function () {
expect(scope.add(2, 3)).toEqual(5);
});
it('test detect function', function () {
expect(scope.detect(4,3)).toEqual(1);
});
it('test pr function', function () {
expect(scope.pr()).toEqual();
});

it('test normal varibles', function () {
expect(scope.testText).toEqual('Hello Jsamine And Karma');
expect(scope.aaa).toBe(1);

});

//测试伪后台的json数据
it('test get json', function () {
$httpBackend.flush();
expect(scope.users.length).toBe(3);
expect(scope.users[0].name).toBe('Bob');
expect(scope.users[1].name).toEqual('Jane');
expect(scope.users[1].id).toBe(2);
expect(scope.users[2].age).toBe(22);

//输出结果以方便查看
for(var i=0;i<scope.users.length;i++){
console.log(scope.users[i].id);
console.log(scope.users[i].name +" "+ scope.users[i].age);
}
});

//测试伪后台网络数据
it('test get network data', function () {
$httpBackend.flush();
expect(scope.userInfo).toBeDefined();
expect(scope.userInfo.isModifyPwd).toEqual('N');
expect(scope.userInfo.retCode).toEqual('000000');
expect(scope.userInfo.phone).toEqual('15250964261');
expect(scope.userInfo.dataList.length).toBe(1);
expect(scope.userInfo.dataList[0].moduleId).toEqual("501");
expect(scope.userInfo.dataList[0].moduleList[0].moduleId).toEqual("501001");
expect(scope.userInfo.dataList[0].moduleList[0].parentModuleId).toEqual("501");
expect(scope.userInfo.dataList[0].moduleList[0].moduleUrl).toEqual("/financing");
expect(scope.userInfo.dataList[0].moduleList[1].moduleId).toEqual("501002");
expect(scope.userInfo.dataList[0].moduleList[1].moduleName).toEqual("融资进度查询");
expect(scope.userInfo.dataList[0].moduleList[1].moduleUrl).toEqual("/myFinancing");
console.log(scope.userInfo);
});

});
});

测试结果:
测试结果

补充:$httpBackend常用方法

when

新建一个后端定义(backend definition)。

1
when(method, url, [data], [headers]);

expect

新建一个请求期望(request expectation)。

1
expect(method, url, [data], [headers]);

when和expect都需要4个参数method, url, data, headers, 其中后2个参数可选。

  • method表示http方法注意都需要是大写(GET, PUT…);

  • url请求的url可以为正则或者字符串;

  • data请求时带的参数,

  • headers请求时设置的header。

如果这些参数都提供了,那只有当这些参数都匹配的时候才会正确的匹配请求。when和expect都会返回一个带respond方法的对象。respond方法有3个参数status,data,headers通过设置这3个参数就可以伪造返回的响应数据了。

区别:

$httpBackend.when与$httpBackend.expect的区别在于:$httpBackend.expect的伪后台只能被调用一次(调用一次后会被清除),第二次调用就会报错,而且$httpBackend.resetExpectations可以移除所有的expect而对when没有影响。