即使有范围,Angularjs单元测试监视回调也不会被调用.$ digest

CTC*_*CTC 7 unit-testing watch angularjs angularjs-scope

我正在努力测试一个监视几个变量的控制器.在我的单元测试中,即使调用scope,我也无法调用$ watch函数的回调.$ digest().看起来这应该很简单,但我没有运气.

这是我在控制器中的内容:

angular.module('app')
  .controller('ClassroomsCtrl', function ($scope, Classrooms) {

    $scope.subject_list = [];

    $scope.$watch('subject_list', function(newValue, oldValue){
      if(newValue !== oldValue) {
        $scope.classrooms = Classrooms.search(ctrl.functions.search_params());
      }
    });
});
Run Code Online (Sandbox Code Playgroud)

这是我的单元测试:

angular.module('MockFactories',[]).
  factory('Classrooms', function(){
    return jasmine.createSpyObj('ClassroomsStub', [
      'get','save','query','remove','delete','search', 'subjects', 'add_subject', 'remove_subject', 'set_subjects'
    ]);
  });

describe('Controller: ClassroomsCtrl', function () {

  var scope, Classrooms, controllerFactory, ctrl;

  function createController() {
    return controllerFactory('ClassroomsCtrl', {
      $scope: scope,
      Classrooms: Classrooms
    });
  }

  // load the controller's module
  beforeEach(module('app'));

  beforeEach(module('MockFactories'));

  beforeEach(inject(function($controller, $rootScope, _Classrooms_ ){
    scope = $rootScope.$new();

    Classrooms = _Classrooms_;

    controllerFactory = $controller;

    ctrl = createController();

  }));

  describe('Scope: classrooms', function(){

    beforeEach(function(){
      Classrooms.search.reset();
    });

    it('should call Classrooms.search when scope.subject_list changes', function(){
      scope.$digest();
      scope.subject_list.push(1);
      scope.$digest();

      expect(Classrooms.search).toHaveBeenCalled();
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

我已经尝试替换所有范围.$ digest()调用范围.$ apply()调用.我试过打电话给他们3到4次,但我无法得到$ watch的回调.

有什么想法可以在这里发生什么?

更新: 这是一个更简单的例子,它不涉及模拟,存根或注入工厂.

angular.module('edumatcherApp')
  .controller('ClassroomsCtrl', function ($scope) {
    $scope.subject_list = [];
    $scope.$watch('subject_list', function(newValue, oldValue){
      if(newValue !== oldValue) {
        console.log('testing');
        $scope.test = 'test';
      }
    });
Run Code Online (Sandbox Code Playgroud)

和单元测试:

it('should set scope.test', function(){
  scope.$digest();
  scope.subject_list.push(1);
  scope.$digest();

  expect(scope.test).toBeDefined();
});
Run Code Online (Sandbox Code Playgroud)

这也失败了"预期未定义的定义".并且没有任何内容记录到控制台.

更新2

我注意到了2个更有趣的事情.

  1. 似乎有一个问题是,在调用回调时newValue和oldValue是相同的.我将两者记录到控制台,它们都等于[].因此,例如,如果我将$ watch函数更改为如下所示:

    $scope.$watch('subject_list', function(newValue, oldValue){
        console.log('testing');
        $scope.test = 'test';
    });
    
    Run Code Online (Sandbox Code Playgroud)

测试顺利通过.不确定为什么newValue和oldValue没有正确设置.

  1. 如果我将$ watch回调函数更改为命名函数,并检查是否曾调用过命名函数,那么也会失败.例如,我可以将我的控制器更改为:

    $scope.update_classrooms = function(newValue, oldValue){
        $scope.test = 'testing';
        console.log('test');
    };
    
    $scope.watch('subject_list', $scope.update_classrooms);
    
    Run Code Online (Sandbox Code Playgroud)

并将我的测试更改为:

it('should call update_classrooms', function(){
  spyOn(scope,'update_classrooms').andCallThrough();
  scope.$digest();
  scope.subject_list.push(1);
  scope.$digest();
  expect(scope.update_classrooms).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

它失败了,"已经调用了预期的间谍update_classrooms".

在这种情况下,update_classrooms肯定会被调用,因为'test'会被记录到控制台.我很困惑.

bro*_*son 8

我刚刚在自己的代码库中遇到了这个问题,答案结果是我需要一个范围.$ digest()在实例化控制器后立即调用.在测试中,您必须在观察变量的每次更改后手动调用范围.$ digest().这包括在构造控制器以记录初始监视值之后.

另外,正如评论中指定的Vitall一样,集合需要$ watchCollection().

在您的简单示例中更改此手表可解决此问题:

$scope.$watch('subject_list', function(newValue, oldValue){
    if(newValue !== oldValue) {
        console.log('testing');
        $scope.test = 'test';
    }
});
Run Code Online (Sandbox Code Playgroud)

至:

$scope.$watchCollection('subject_list', function(newValue, oldValue){
    if(newValue !== oldValue) {
        console.log('testing');
        $scope.test = 'test';
    }
});
Run Code Online (Sandbox Code Playgroud)

我做了一个jsfiddle来证明它的区别:

http://jsfiddle.net/ydv8k4zy/ - 原始测试失败 - 因使用$ watch而失败,而不是$ watchCollection

http://jsfiddle.net/ydv8k4zy/1/ - 功能版

http://jsfiddle.net/ydv8k4zy/2/ - $ watchCollection在没有初始范围的情况下失败.$ digest.

如果您在第二个失败的项目上使用console.log,您将看到在范围之后使用相同的旧值和新值调用监视.$ digest()(第25行).