【问题标题】:Angular unit testing, what's causing the $rootScope.$watch() to fire?角度单元测试,是什么导致 $rootScope.$watch() 触发?
【发布时间】:2016-11-24 14:19:12
【问题描述】:

这是我的控制器。

(function() {
    'use strict';

    app.controller('NavController', navController);

    function navController($rootScope, $scope, $location, $window, NavFactory) {

        $scope.menuConfig = null;
        $scope.menuItems = [];
        $scope.isActive = isActive;
        $scope.toggleBurgerMenu = toggleBurgerMenu;
        $scope.toggleDeviceSettings = toggleDeviceSettings;

        $rootScope.$watch('dictionary', function(dictionary) {
            if (dictionary) {
                init();
            }
        });

        function init() {
            $scope.menuConfig = $rootScope.config.navigation;
            NavFactory.GetMenuItems().then(onGetMenuItems, $scope.onAPIError);
        }

        function onGetMenuItems(response) {
            $scope.moduleConfig = response.moduleConfig;
            $scope.menuItems = response.menuItems;
        }
    }
})();

这是我的测试套件(业力,茉莉) describe('NavController 函数', function() {

var $rootScope, $scope, $location, $window, $controller, createController, NavFactory, CacheFactory, toastr;

beforeEach(module('mockedDashboard'));

beforeEach(inject(function(_$rootScope_, _$controller_, _$location_, _$window_, _NavFactory_, _toastr_) {
    $location = _$location_;
    $window = _$window_;
    toastr = _toastr_;

    $rootScope = _$rootScope_;
    $rootScope.dictionary = jsDictionary;
    $rootScope.config = jsConfig;
    $rootScope.config.contentFolder = '_default';

    $scope = _$rootScope_.$new();
    $scope.burgerMenuActive = false;
    //mock the parent controller function
    $scope.navigate = function(path) {
        $location.path('/' + path);
    };
    // end mock

    createController = function() {
        return _$controller_('NavController', {
            '$scope': $scope
        });
    };
    $controller = createController();
}));

// We are using CacheFactory in this project, when running multiple tests on the controller
// we need to destroy the cache for each test as the controller is initialized for each test.
afterEach(inject(function(_CacheFactory_) {
    CacheFactory = _CacheFactory_;
    CacheFactory.destroy('defaultCache');
}));

describe('init()', function() {

    it('should call NavFactory.GetMenuItems and set moduleConfig and menuItems on $scope', inject(function(_$httpBackend_) {

        var $httpBackend = _$httpBackend_;

        expect($scope.moduleConfig).toBe(undefined);
        expect($scope.menuItems.length).toBe(0);

        var responseData = [{
            "path": "stats",
            "url": "./app/modules/dashboard/modules/score/score.index.html",
            "icon": "fa fa-fw fa-pie-chart",
            "dashboardEnabled": true,
            "burgerMenu": true,
            "barMenu": false,
            "barMenuOrder": -1,
            "bottomMenu": true,
            "bottomMenuOrder": 3,
            "order_sm": 1,
            "order_md": 2,
            "order_lg": 2
        }];

        $httpBackend.expectGET('../content/_default/config/modules.json').respond(responseData);
        $httpBackend.flush();

        expect($scope.moduleConfig).not.toBe(undefined);
        expect($scope.menuItems.length > 0).toBe(true);
    }));
});});

我原以为必须运行 $scope.$digest() 才能触发观察程序,让 init() 运行,但它运行良好并且测试通过了。

这不是问题,我只是想了解它为什么会运行,因为我不了解观察者是如何触发的?是什么导致它运行?

【问题讨论】:

  • $rootScope.dictionary = jsDictionary;中的jsDictionary从何而来?
  • 通常,它来自引导程序中的一个 json 文件,但出于测试目的,它是一个加载了测试文件的全局 javascript 对象。它很脏,但是当我学习解决我/我们在构建我们的应用程序时犯的其他错误时它可以工作。所有这些单元测试都是在构建之后完成的。我们学到了很多东西,未来我们将采用 TDD 方法。

标签: angularjs unit-testing karma-runner


【解决方案1】:

在我看来,在全局范围内初始化控制器是个坏主意。这是因为,将来,当您想要测试一些逻辑上独立的其他控制器时,您仍然会为每个 it() 块初始化每个控制器,我认为没有理由这样做。

至于为什么您的$watch 被解雇,您将jsDictionary 设置为$rootScope 并为每个it() 块初始化它。在您的控制器代码中,对于$watch,您只是检查该值是否存在,它会在您在前几行中设置它并因此调用init() 块。使用 $watch 的正确方法是:

$rootScope.$watch('dictionary', function(oldVal, newVal){
});

更好的做法是为每个控制器创建一个describe() 块,将您的beforeEach() 内容放入describe() 以及您的it() 块中,这些内容与该特定控制器相关。这也将帮助您构建模块化测试用例。

【讨论】:

  • 我见过这样设置的手表,我也是这样做的。字典手表被设置为检查它是否存在,因为我们在设置它之前使用它有种族问题,所以,我们像这样等待它......然而,这已经解决了,但我需要工作的代码库是在这个问题仍然存在的另一个代码分支上,因此我检查它是否存在。我不认为观察者会在$digest() 被调用之前运行? $digest() 是在某处自动调用的吗?
  • 您能解释一下在全局范围内初始化控制器是什么意思吗?这个控制器被包裹在 IIFE 中,而不是在 $rootScope 上?
  • Angular 有很多内置函数可以自动触发$digest()。异步调用就是其中之一。在您的代码中,$httpBackend.flush() 触发摘要循环。
  • 我说的是测试用例中的全局范围,而不是实际的控制器代码。
  • 啊,感谢您的澄清。你已经回答了我的两个问题 :) 我现在已经将 createController 函数放在全局范围内,并通过参数创建它,所以现在每个控制器都在他们自己的描述中在测试套装中初始化,所以它们不是全局的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多