$ watch回调每次在浏览器中触发,但在测试期间只触发一次

时间:2016-05-03 11:25:47

标签: javascript angularjs unit-testing

我的代码应该存储有关过滤器更改的信息。 在浏览器$watch中,每次更改值时都会触发回调。 然而,当我试图在jasmine / karma测试中复制它时,它会在第一次更改后立即触发。

控制器

var tableFilter = this
  ;

var init0 = true
  ;
$scope.$watch( 'tableFilter.config.period', function () {
  console.log('watch');
  if ( !init0 ) {
    console.log('set dirty');
    tbFilterConfig.set( 'pristine', false );
    tbFilterConfig.setDirty( 'period' );
  }
  init0 = false;
} );

测试

describe( '$scope $watch', function () {
  it( 'should add period to dirty after second change', function () {
    $scope.$apply(console.log(1), controller.config.period = 'test');
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [] );
    $scope.$apply(console.log(2), controller.config.period = 'test2');
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [ 'period' ] );
  } );
});

控制台输出:

LOG: 1
LOG: 'watch'
LOG: 2
PhantomJS 1.9.8 (Windows 8 0.0.0) Controller: TableFilterCtrl $scope $watch should add period to dirty after second change FAILED
Expected [  ] to equal [ 'period' ].
        at C:/Projects/trackback-network-insight-ui/test/spec/controllers/table_filter.js:74

但是在浏览器中:

setTimeout(function (  ) {
  $scope.$apply(console.log(1), tableFilter.config.period = 'test');
  $scope.$apply(console.log(2), tableFilter.config.period = 'test2');
}, 1000);

给我:

LOG: watch
LOG: 1
LOG: watch
LOG: set dirty
LOG: 2
LOG: watch
LOG: set dirty

这是预期的行为。

我的考试有什么问题呢?提前谢谢!

修改

完整控制器代码:

angular.module( 'insightApp' )
.controller( 'TableFilterCtrl', [ '$rootScope', 'tbConfig', '$scope', '$http', 'API_PATH', 'uiAlerts', 'tbFilterConfig',
  function ( $rootScope, tbConfig, $scope, $http, API_PATH, uiAlerts, tbFilterConfig ) {

    var tableFilter = this
      , newData = {}
      ;

    /* EXTRACT INITIAL DATA OR LOAD DATA IF TYPE OF DATA IS STRING */
    /* FETCH !!SELECT!! DATA FROM API */
    function fetchData( url, name ) {
      return $http.post( API_PATH + url, tbFilterConfig.get() )
        .success( function ( data ) {
          newData[ name ] = data;
        } )
        .error( function ( error ) {
          uiAlerts.set( {
            error: 'Ooops! We were unable to get filter data.'
          } );
          console.error( error );
        } );
    }

    tableFilter.filters = tbConfig.get( 'filters' );
    tableFilter.reports = tbConfig.get( 'reports' );
    tableFilter.config = tbFilterConfig.getRaw();

    /* RESET VALUES OF ALL DEPENDENT FILTERS */
    function resetDependent( name ) {
      var filters = tbConfig.get( 'filters' )
        ;
      filters.forEach( function ( filter ) {
        if ( filter.dependencies && filter.dependencies.indexOf( name ) > -1 ) {
          if ( tbFilterConfig.get( filter.key ) !== filter.defaultVal ) {
            tbFilterConfig.set( filter.key, filter.defaultVal );
            tbFilterConfig.setDirty( filter.key );
          }
        }
      } );
    }

    /* GET INITIAL DATA FOR SELECT OPTIONS */
    tableFilter.getData = function ( name, data, defaultVal ) {
      if ( !(name in newData) ) {
        newData[ name ] = [];
        if ( typeof data === 'string' ) {
          fetchData( data, name );
        } else if ( typeof data === 'object' ) {
          newData[ name ] = data;
        } else {
          console.error( 'Unexpected data type:', typeof data, data );
        }
        if ( !tbFilterConfig.get( name ) ) {
          tbFilterConfig.set( name, defaultVal );
        }
        var init = true;
        $scope.$watch( 'tableFilter.config.' + name, function () {
          if ( !init ) {
            resetDependent( name );
            tbFilterConfig.set( 'pristine', false );
            tbFilterConfig.setDirty( name );
          }
          init = false;
        } );
      }
      return newData[ name ];
    };

    /* WATCH FIXED PROPERTIES: PERIOD, DATE_FROM, DATE_TO */
    var init0 = true
      , init1 = true
      , init2 = true
      ;

    $scope.$watch( 'tableFilter.config.period', function () {
      if ( !init0 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'period' );
      }
      init0 = false;
    } );

    /* WATCH FIXED PROPERTIES DATE_FORM */
    $scope.$watch( 'tableFilter.config.date_from', function () {
      if ( !init1 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'date_from' );
      }
      init1 = false;
    } );
    /* WATCH FIXED PROPERTIES DATE_TO */
    $scope.$watch( 'tableFilter.config.date_to', function () {
      if ( !init2 ) {
        tbFilterConfig.set( 'pristine', false );
        tbFilterConfig.setDirty( 'date_to' );
      }
      init2 = false;
    } );

    /* UPDATE FILTER DATA */
    tableFilter.updateSelectData = function ( url, name, dependencies ) {
      if ( typeof url === 'string' ) {
        var touched = false;
        /* CHECK IF DEPENDENCIES CHANGED */
        for ( var i = 0; i < dependencies.length; i++ ) {
          if ( tableFilter.config.dirty.indexOf( dependencies[ i ] ) > -1 ) {
            touched = true;
            break;
          }
        }
        /* IF DEPENDENCIES CHANGED GET NEW DATA */
        if ( touched ) {
          return fetchData( url, name );
        }
      }
    };
  } ] );

并测试:

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

// load the controller's module

beforeEach( function () {
  module( 'insightApp' );
} );

var controller
  , $scope
  , alerts
  , tbFilterConfigObj
  ;

// Initialize the controller and a mock scope
beforeEach( inject( function ( $controller, $rootScope, uiAlerts, tbFilterConfig ) {
  tbFilterConfigObj = tbFilterConfig;
  $scope = $rootScope.$new();
  alerts = uiAlerts;
  controller = $controller( 'TableFilterCtrl', {
    $scope: $scope
  } );
  $scope.$apply();
} ) );


describe( '$scope $watch', function () {
  it( 'should add period to dirty after second change', function () {
    $scope.$apply( console.log( 1 ), controller.config.period = 'test' );
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [] );
    $scope.$apply( console.log( 2 ), controller.config.period = 'test2' );
    expect( tbFilterConfigObj.get( 'dirty' ) ).toEqual( [ 'period' ] );
  } );
} );

});

EDIT2

我很抱歉代码质量,但它尚未生产。

1 个答案:

答案 0 :(得分:0)

当AngularJS在您的浏览器中运行时,它会运行digest个周期来触发所有观察者及其回调。这就是使Angular起作用的原因,它会自动发生。

这种机制在单元测试中不起作用。运行测试时,您需要通过调用$apply$digest手动触发摘要机制。

让我们分解您的测试规范中发生的情况(在控制台日志中很好地显示):

  1. $scope.$apply(console.log(1), controller.config.period = 'test'); - &gt;将1写入控制台;更改config.period的值。

  2. $scope.$apply(console.log(2), controller.config.period = 'test2'); - &gt;由于tableFilter.config.period,首次触发$apply 的观察者回调;将2写入控制台;更改config.period.

  3. 的值

    此后第二次没有任何东西触发观察者的回调。

    解决方案很简单:在每个$scope.$digest()语句之前添加对$scope.$apply()(或expect)的调用。