Javascript 如何正确测试AngularJS控制器功能

Javascript 如何正确测试AngularJS控制器功能,javascript,angularjs,jasmine,Javascript,Angularjs,Jasmine,我们刚刚开始在AngularJS项目中实施jasmine测试,我有一个问题: 我们要测试此控制器功能: $scope.deleteClick = function () { $scope.processing = true; peopleNotesSrv.deleteNote($scope.currentOperator.operatorId, $scope.noteId, $scope.deleteSuccessCallback, $scope.deleteEr

我们刚刚开始在AngularJS项目中实施jasmine测试,我有一个问题:

我们要测试此控制器功能:

$scope.deleteClick = function () {
        $scope.processing = true;
        peopleNotesSrv.deleteNote($scope.currentOperator.operatorId, $scope.noteId, $scope.deleteSuccessCallback, $scope.deleteErrorCallback);
    };
我们编写了以下测试:

    it('deleteClick should pass proper parameters to peopleNoteSrv', function () {
        $controllerConstructor('PeopleNoteEditCtrl', { $scope: $scope });

        $scope.noteId = 5;

        expect(function () { $scope.deleteClick(); }).not.toThrow();
    });
it('should call Service properly', function () {
  expect(scope.doIt()).toBe("OK");
});
此测试确保在调用$scope.deleteClick()函数时,$scope.processing设置为true,并且对peopleNotesSrv的调用不会因为无效参数而引发任何错误。我们在单独的测试中测试这两个回调函数


我们是否应该测试调用了peopletesrv.deleteNote函数,以便测试更加明确?目前编写此测试的方式并没有真正告诉某人deleteClick()函数在引擎盖下的作用,这似乎是不正确的。

我认为答案是这取决于它

有两种情况:


1-您还有一套针对PeopleNotesSV服务的测试

在本例中,我将保持此测试不变,或者检查$scope.deleteClick()的特定功能的更多内容,例如$scope.processing上是否有任何监视程序执行与.deleteClick()调用相关的任何特定操作


2-您没有对PeopleNotesSV服务的所有可能功能进行任何测试

在本例中,我将编写一个更显式的测试来检查.deleteNote()是否实际执行了它的任务


在我看来,你真的应该建立测试,尽量不要在多个地方测试同一件事情,因为这会增加额外的工作,并且可能会在测试中产生漏洞,如果你认为,“我可以在从调用它的特定函数调用这个特定情况时测试它。”

如果您想在不同的地方将deletNote()作为更大的函数的一部分重用,那么您需要为相同的代码编写另一个测试,因为它是从不同的函数调用的

因此,我将针对案例1,通过这种方式,您可以为该服务编写所有测试,然后相信这些测试涵盖了该特定测试的其余部分。如果您在错误的输入上抛出错误,或者在实际删除注释时失败,那么您应该信任其他代码来测试它设计用来测试的内容。这将大大加快您的测试编写时间,并增加您的测试覆盖所有案例的机会。它还将该服务的所有测试保持在测试代码中的同一位置

我想一个好问题是,这是什么样的测试?单元测试还是端到端测试

我假设这是一个针对我的答案的单元测试,如果这是一个端到端测试,那么您可能希望继续跟踪函数调用,以验证一切是否按照预期进行

这里有一些关于单元测试、端到端测试的链接,以及一篇关于和的非常好的文章

  • (端到端测试也可以称为集成测试)

问问自己,如果您使用TDD开发了它,您会怎么做。这与Sam指出的方向差不多,但以下是一些例子:

控制器测试
  • 开始编写一个预期deleteClick存在的测试
  • 期望deleteClick设置加载状态(检查处理是否为true)
  • 测试是否将服务注入控制器(peopleNotesSrv)
  • 检查deleteClick是否调用该服务(通过spies已经提到)
  • 验证是否存在并设置了$scope.noteId和其他$scope.params
  • 这与控制器有关。应在Service.spec中测试所有标准,无论它是否失败或抛出错误等。因为我不知道你们的服务细节,这里有一些例子

    服务测试
  • 确保deleteNote存在
  • 检查如果提供了错误数量的参数(更少或更多),会发生什么情况
  • 做一些阳性测试(比如你的noteId=5)
  • 做一些阴性测试
  • 确保正确调用回调
  • 。。。等等

    在控制器中进行有效性测试没有多大意义,因为你需要为每个控制器进行测试。通过将服务隔离为单独的测试单元,并确保它满足所有需求,您可以不用测试就直接使用它。这与您永远不会测试jQuery特性或Angular jQLite的情况是一样的,因为您只是希望它们做它们应该做的事情:)

    编辑:

    使控制器测试在服务调用时失败 很简单,让我们举个例子。首先,我们创建服务测试,以确保在未提供正确数量的参数时调用失败:

    describe('Service: peopleNoteSrv', function () {
    
      // load the service's module
     beforeEach(module('angularControllerServicecallApp'));
    
     // instantiate service
     var peopleNoteSrv;
     beforeEach(inject(function (_peopleNoteSrv_) {
       peopleNoteSrv = _peopleNoteSrv_;
     }));
    
     it('should throw error on false number of arguments', function () {
       expect(function() { peopleNoteSrv.deleteNote('justOneParameter'); }).toThrow();
     });
    
    });
    
    现在,为了确保测试通过,让我们在服务方法中创建错误抛出部分

    angular.module('angularControllerServicecallApp')
      .service('peopleNoteSrv', function peopleNoteSrv() {
    
        this.deleteNote = function(param1, param2, param3) {
          if(arguments.length !== 3)
            throw Error('Invalid number of arguments supplied');
          return "OK";
        };
    });
    
    现在让我们创建2个演示控制器,FirstCtrl将正确执行,但SecondCtrl将失败

    angular.module('angularControllerServicecallApp')
      .controller('FirstCtrl', function ($scope, peopleNoteSrv) {
        $scope.doIt = function() {
          return peopleNoteSrv.deleteNote('param1', 'param2', 'param3');
        }
      });
    
    angular.module('angularControllerServicecallApp')
      .controller('SecondCtrl', function ($scope, peopleNoteSrv) {
        $scope.doIt = function() {
          return peopleNoteSrv.deleteNote('onlyOneParameter');
        }
      });
    
    作为演示,两个控制器都进行了以下测试:

        it('deleteClick should pass proper parameters to peopleNoteSrv', function () {
            $controllerConstructor('PeopleNoteEditCtrl', { $scope: $scope });
    
            $scope.noteId = 5;
    
            expect(function () { $scope.deleteClick(); }).not.toThrow();
        });
    
    it('should call Service properly', function () {
      expect(scope.doIt()).toBe("OK");
    });
    
    因果报应现在吐出这样的东西:

    Error: Invalid number of arguments supplied
        at [PATH]/app/scripts/services/peoplenotesrv.js:15
        at [PATH]/app/scripts/controllers/second.js:13
        at [PATH]/test/spec/controllers/second.js:20
    
    因此,您确切地知道您没有更新SecondCtrl。当然,这应该适用于使用服务方法的任何测试


    希望这就是你的意思。

    是的,你可以模拟
    peopletesrv
    服务并监视deletNote方法……我们主要关心的是在服务上的签名更改时捕获错误。假设我们在服务调用中添加了一个新参数。我们希望现在调用该服务的所有控制器测试在没有正确参数的情况下都失败。你会怎么做呢?好的,我已经更新了我以前的答案,希望这就是你的意思