为AngularJS控制器编写单元测试

介绍完Karma(测试运行器)和Jasmine(测试框架)后,让我们看看怎样为AngularJS创建的控制器编写测试。看一个非常简单的控制器,类似于在前面章节看到的:

angular.module('notesApp', [])
  .controller('ListCtrl', [function() {

    var self = this;
    self.items = [
      {id: 1, label: 'First', done: true},
      {id: 2, label: 'Second', done: false}
    ];

    self.getDoneClass = function(item) {
      return {
        finished: item.done,
        unfinished: !item.done
      };
    };
}]);

上述实例是一个非常简单的控制器。它只是给控制器实例分配一个数组(范围内的HTML可访问它),然后有一个函数表示逻辑,它返回基于项目的完成状态的实际类。让我们看看对于这种控制器Jasmine是怎样来编写测试的:

describe('Controller: ListCtrl', function() {
   
  // Instantiate a new version of my module before each test
  beforeEach(module('notesApp'));
 
  var ctrl;
  // Before each unit test, instantiate a new instance
  // of the controller
  beforeEach(inject(function($controller) {
    ctrl = $controller('ListCtrl');
  }));
 
  it('should have items available on load', function() {
    expect(ctrl.items).toEqual([
      {id: 1, label: 'First', done: true},
      {id: 2, label: 'Second', done: false}
    ]);
  });
 
  it('should have highlight items based on state', function() {
    var item = {id: 1, label: 'First', done: true};
 
    var actualClass = ctrl.getDoneClass(item);
    expect(actualClass.finished).toBeTruthy();
    expect(actualClass.unfinished).toBeFalsy();
 
    item.done = false;
    actualClass = ctrl.getDoneClass(item);
    expect(actualClass.finished).toBeFalsy();
    expect(actualClass.unfinished).toBeTruthy();
  });
 
});

在这个例子中ListCtrl控制器有两个特殊的单元测试。让我们逐个看一看每个单元测试的有趣之处。

实例化一个模块

作为描述块的一部分我们为ListCtrl做的第一件事变是新建一个AngularJS模块notesApp实例。这会在每个特定单元测试之前影响刚加载的所有与该模块相关的控制器、服务、命令和过滤器。它的优势在于您在一个单元测试设置修改的状态和不能影响另一个单元测试。每个单元测试本质上变得独立和易控制。和其它函数一样,module方法是AngularJS库提供的文件angular-mocks的辅助方法之一。

注入服务

我们有一个变量ctrl,这将在每一个单元测试的控制器实例。 beforeEach块之后使用所谓的注入,基本上都是用Jasmine在beforeEach及it块中的函数注入AngularJS服务。我们将在后面章节详细讲解AngularJS服务。但我们要知道有一个名为$AngularJS服务,可以使用它来实例化控制器。函数可以通过多个参数注入,每一个都参数都是AngularJS后来创建并注入到函数的服务。
创建我们的控制器实例

我们使用$controller服务来创建ListCtrl控制器实例。仅仅通过传入控制器的名称作为$controller服务字符串,并返回一个新控制器实例。然后指定给变量ctrl,我们将在每个独立的测试中使用它。

为构造函数编写一个测试

无论我们在$controller函数定义什么都会在控制器实例化时执行。我们能为$controller函数做的仅有事情就是设置item数组和函数。所以第一个it块测试条目数组的状态,检查实例化及其值是否正确。它使用了Jasmine toEqual匹配器来检查条目数组是否与我们期待的完全相同。

为getDoneClass函数编写测试

我们最后编写测试检查为控制器实例定义的getDoneClass函数。通过实例化一些本地测试状态(item对象),再传递给控制器getDoneClass函数并存储它的返回值。接下来,我们检查完成与
未完成两种状态的真假值。然后我们改变项目的状态,将完成改成未完成 (设置为false),然后检查函数是否改变相应的返回值。

上个例子的执行顺序如下:

  1. 执行加载AngularJS模块的beforeEach块。
  2. 执行创建控制器的beforeEach块。
  3. 执行“加载项”it代码块。
  4. 执行加载AngularJS模块的beforeEach块。
  5. 执行创建控制器的beforeEach块。
  6. 执行getDoneClass it块。

因此每个单元测试执行时得到一个干净的状态,并确保一个单元测试的修改和状态更改不影响另一个单元测试。

运行单元测试

我们应该如何运行这些单元测试呢?最简单的方法是从命令行/终端/主机执行以下命令 :

karma start

Karma开始从执行命令的目录自动查找karma.conf的命令。并从该文件获取配置。如果您的配置文件不是命名为karma.conf.js,或者如果它在一个不同的文件夹中,您可以将它作为命令的参数。如下命令:

karma start my.conf.js

在我们的例子中这不是必需的。

这两个命令都会读取配置文件,占用正在测试的文件,启动指定的端口的服务器,然后尝试打开在配置中列出的浏览器(为他们提供安装启动器插件)。

Karma命令启动用于服务测试文件的测试服务器。打开连接到服务器的每个浏览器顶部会显示一个绿色栏,表明它已经准备好运行测试。如果配置中没有选择任何浏览器,或自动捕获浏览器遇到一些问题,您仍然可以通过在浏览器中打开指定的URL手动地捕获任何浏览器(在任何机器上)。

从控制台/命令窗口执行karma start命令,打印测试结果。用来捕获的浏览器本身不打印结果,这是因为浏览器可能在一个完全不同的机器上执行测试。运行成功后在控制台/命令窗口应该打印如下截图:

打印测试结果

如果您已经在您的配置将autoWatch设置为true,Karma会在所有的浏览器中自动触发一个测试运行,您可以每次捕捉到测试文件的任何变化。如果没有将autoWatch设置为true,您也可以手动在控制台/终端执行以下命令来触发一个测试运行:

karma run

该命令告诉Karma基于配置为当前活跃的服务器再次执行测试。

不完全译自《AngularJS-Up and Running》。

发表评论