describe("A spec (with setup and tear-down)", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); });
首先任何一个测试用例以describe函数来定义,它有两参数,第一个用来描述测试大体的中心内容,第二个参数是一个函数,里面写一些真实的测试代码
it是用来定义单个具体测试任务,也有两个参数,第一个用来描述测试内容,第二个参数是一个函数,里面存放一些测试方法
expect主要用来计算一个变量或者一个表达式的值,然后用来跟期望的值比较或者做一些其它的事件
beforeEach与afterEach主要是用来在执行测试任务之前和之后做一些事情,上面的例子就是在执行之前改变变量的值,然后在执行完成之后重置变量的值
开始单元测试
下面分别以控制器,指令,过滤器和服务四个部分来编写相关的单元测试。项目地址为angular-seed(点我)项目,可以下载demo并运行其测试用例。
demo中是一个简单的todo应用,会包含一个文本输入框,其中可以编写一些笔记,按下按钮可以将新的笔记加入笔记列表中,其中使用notesfactory封装LocalStorage来储存笔记信息。
先介绍一下angular中测试相关的组件angular-mocks。
了解angular-mocks
在Angular中,模块都是通过依赖注入来加载和实例化的,因此官方提供了angular-mocks.js测试工具来提供模块的定义、加载,依赖注入等功能。
其中一些常用的方法(挂载在window命名空间下):
angular.mock.module: module用来加载已有的模块,以及配置inject方法注入的模块信息。具体使用如下:
beforeEach(module('myApp.filters')); beforeEach(module(function($provide) { $provide.value('version', 'TEST_VER'); }));
该方法一般在beforeEach中使用,在执行测试用例之前可以获得模块的配置。
angular.mock.inject: inject用来注入配置好的ng模块,来供测试用例里进行调用。具体使用如下:
it('should provide a version', inject(function(mode, version) { expect(version).toEqual('v1.0.1'); expect(mode).toEqual('app'); }));
其实inject里面就是利用angular.inject方法创建的一个内置的依赖注入实例,然后里面的模块和普通的ng模块的依赖处理是一样的。
Controller部分
Angular模块是todoApp,控制器是TodoController,当按钮被点击时,TodoController的createNote()函数会被调用。下面是app.js的代码部分。
var todoApp = angular.module('todoApp',[]); todoApp.controller('TodoController',function($scope,notesFactory){ $scope.notes = notesFactory.get(); $scope.createNote = function(){ notesFactory.put($scope.note); $scope.note=''; $scope.notes = notesFactory.get(); } }); todoApp.factory('notesFactory',function(){ return { put: function(note){ localStorage.setItem('todo' + (Object.keys(localStorage).length + 1), note); }, get: function(){ var notes = []; var keys = Object.keys(localStorage); for(var i = 0; i < keys.length; i++){ notes.push(localStorage.getItem(keys[i])); } return notes; } }; });
在todoController中用了个叫做notesFactory的服务来存储和提取笔记。当createNote()被调用时,会使用这个服务将一条信息存入LocalStorage中,然后清空当前的note。因此,在编写测试模块是,应该保证控制器初始化,scope中有一定数量的笔记,在调用createNote()之后,笔记的数量应该加一。
具体的单元测试如下:
describe('TodoController Test', function() { beforeEach(module('todoApp')); // 将会在所有的it()之前运行 // 我们在这里不需要真正的factory。因此我们使用一个假的factory。 var mockService = { notes: ['note1', 'note2'], //仅仅初始化两个项目 get: function() { return this.notes; }, put: function(content) { this.notes.push(content); } }; // 现在是真正的东西,测试spec it('should return notes array with two elements initially and then add one', inject(function($rootScope, $controller) { //注入依赖项目 var scope = $rootScope.$new(); // 在创建控制器的时候,我们也要注入依赖项目 var ctrl = $controller('TodoController', {$scope: scope, notesFactory:mockService}); // 初始化的技术应该是2 expect(scope.notes.length).toBe(2); // 输入一个新项目 scope.note = 'test3'; // now run the function that adds a new note (the result of hitting the button in HTML) // 现在运行这个函数,它将会增加一个新的笔记项目 scope.createNote(); // 期待现在的笔记数目是3 expect(scope.notes.length).toBe(3); }) ); });
在beforeEach中,每一个测试用例被执行之前,都需要加载模块module("todoApp") 。