angularjs - 如何测试父元素对组件绑定(bind)的更改?

标签 angularjs unit-testing jasmine components angularjs-components

我有一个如下组件,想测试 $onChange方法在绑定(bind) myBinding 的情况下执行变化。

我尝试了整个上午,但找不到解决此问题的方法。

angular
    .module('project.myComponent', [])
    .component('myComponent', {
        bindings: {
            myBinding: '<'
        },
        template: '<div>{{$ctrl.result}}</div>',
        controller: myComponentController
    });

function myComponentController($filter, someService) {
    var ctrl = this;
    ctrl.result = 0;

    $ctrl.$onChange = function (changes) {
        if(angular.isDefined(changes.myBinding)) {
            if(angular.isDefined(changes.myBinding.currentValue)) {
                if(angular.isDefined(changes.myBinding.currentValue != changes.myBinding.previousValue)) {
                    myService.doSomething(changes.myBinding.currentValue).then(
                        function(data) {
                            ctrl.result = changes.myBinding.currentValue * 3;
                        }
                    );                  
                }
            }
        }
    }
}

我希望我的测试表现得像改变绑定(bind)值的组件父级一样。
require('angular-mocks');

describe('myComponment', function() {
    var element, scope;

    beforeEach(inject(function(_$rootScope_, _$compile_) {

    }));

    fit('should display the controller defined title', function() {        
        // prepare test and set myBinding to 10
        expect(component.result).toBe(30);
    });
});

那可能吗?如何?
有什么提示吗? Plunker、CodePen 或其他示例?

最佳答案

测试 AngularJS 组件与测试指令没有太大区别。

要测试 Controller 的方法/属性,您可以使用 element.controller("componentName") method 访问组件 Controller 的实例。 ( componentName - 是一个驼峰式指令/组件名称)。

这是使用 $compile service 的示例测试组件和$onChanges钩:

angular.module('myApp', [])
.component('myComponent', {
    bindings: {
        myBinding: '<'
    },
    template: '<div>{{$ctrl.result}}</div>',
    controller: 'myComponentController'
})
.controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
    var ctrl = this;

    ctrl.$onInit = onInit;
    ctrl.$onChanges = onChanges;

    function onInit() {
        ctrl.result = ctrl.myBinding;
    }

    function onChanges(changes) {
        if (angular.isDefined(changes.myBinding)) {
            if (angular.isDefined(changes.myBinding.currentValue)) {
                if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
                    myService.doSomething(changes.myBinding.currentValue).then(
                        function (data) {
                            ctrl.result = data; 
                        }
                    );
                }
            }
        }
    }
}])
.service('myService', ['$timeout', function ($timeout) {
    return {
        doSomething: function (x) {
            return $timeout(function () {
                return x * 3;
            }, 500);
        }
    };
}]);


/*
TEST GO HERE 
*/

describe('Testing a component controller', function() {
  var $scope, ctrl, $timeout, myService;
  
    beforeEach(module('myApp', function ($provide) {

    }));
  
    beforeEach(inject(function ($injector) {
        myService = $injector.get('myService');
        $timeout = $injector.get('$timeout');
    }));
  
    describe('with $compile', function () { 
        var element;
        var scope;
        var controller;
        
        beforeEach(inject(function ($rootScope, $compile) {
            scope = $rootScope.$new();
            scope.myBinding = 10;
            element = angular.element('<my-component my-binding="myBinding"></my-component>');
            element = $compile(element)(scope);
            controller = element.controller('myComponent');
            scope.$apply();
        }));
      
        
         it('should render template', function () {
           expect(element[0].innerText).toBe('10'); //initial
           $timeout.flush(); //onchanges happened and promise resolved from the service
           //undefined -> 10
           expect(element[0].innerText).toBe('30'); 
         });
         
         
         it('should reflect to changes', function () {
           spyOn(myService, "doSomething").and.callThrough();
           scope.myBinding = 15; //change the binding
           scope.$apply(); //we need to call $apply to pass the changes down to the component
           $timeout.flush();
           expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called 
           expect(controller.result).toBe(45); // check controller's result value 
         });
         
    })

});
.as-console-wrapper {
  height:0;
}
<!DOCTYPE html>
<html>

  <head>
    <!-- jasmine -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
    <!-- jasmine's html reporting code and css -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
    <link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
    
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
    <!-- angular itself -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
    <!-- angular's testing helpers -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
  </head>

  <body>
    <!-- bootstrap jasmine! -->
  <script>
    var jasmineEnv = jasmine.getEnv();
    
    // Tell it to add an Html Reporter
    // this will add detailed HTML-formatted results
    // for each spec ran.
    jasmineEnv.addReporter(new jasmine.HtmlReporter());
    
    // Execute the tests!
    jasmineEnv.execute();
  </script>
  </body>

</html>


您还可以使用 $componentController service 测试您的组件。 .但在这种情况下,您需要在测试中显式调用生命周期 Hook ,例如:
ctrl = $componentController('myComponent', {$scope: scope}, { myBinding: 10 });
ctrl.$onInit();

测试$onChanges钩子(Hook),您将需要传递一个“正确”构造的更改对象作为参数:

angular.module('myApp', [])
    .component('myComponent', {
        bindings: {
            myBinding: '<'
        },
        template: '<div>{{$ctrl.result}}</div>',
        controller: 'myComponentController'
    })
    .controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
        var ctrl = this;

        ctrl.$onInit = onInit;
        ctrl.$onChanges = onChanges;

        function onInit() {
            ctrl.result = ctrl.myBinding;
        }

        function onChanges(changes) {
            if (angular.isDefined(changes.myBinding)) {
                if (angular.isDefined(changes.myBinding.currentValue)) {
                    if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
                        myService.doSomething(changes.myBinding.currentValue).then(
                            function (data) {
                                ctrl.result = data;
                            }
                        );
                    }
                }
            }
        }
    }])
    .service('myService', ['$timeout', function ($timeout) {
        return {
            doSomething: function (x) {
                return $timeout(function () {
                    return x * 3;
                }, 500);
            }
        };
    }]);


/*
TEST GO HERE 
*/

describe('Testing a component controller', function () {
    var $scope, ctrl, $timeout, myService;

    beforeEach(module('myApp', function ($provide) {

    }));

    beforeEach(inject(function ($injector) {
        myService = $injector.get('myService');
        $timeout = $injector.get('$timeout');
    }));

    describe('with $componentController', function () {
        var scope;
        var controller;

        beforeEach(inject(function ($rootScope, $componentController) {
            scope = $rootScope.$new();
            scope.myBinding = 10;

            controller = $componentController('myComponent', {$scope: scope}, {myBinding: 10});
            controller.$onInit();
        }));

        it('should reflect to changes', function () {
            spyOn(myService, "doSomething").and.callThrough();
            controller.$onChanges({myBinding: {currentValue: 15, previousValue: 10}});
            $timeout.flush(); // resolve service promise 
            expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called 
            expect(controller.result).toBe(45); // check controller's result value 
        });

    })

});
.as-console-wrapper {
  height:0;
}
<!DOCTYPE html>
<html>

  <head>
    <!-- jasmine -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
    <!-- jasmine's html reporting code and css -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
    <link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
    
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
    <!-- angular itself -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
    <!-- angular's testing helpers -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
  </head>

  <body>
    <!-- bootstrap jasmine! -->
  <script>
    var jasmineEnv = jasmine.getEnv();
    
    // Tell it to add an Html Reporter
    // this will add detailed HTML-formatted results
    // for each spec ran.
    jasmineEnv.addReporter(new jasmine.HtmlReporter());
    
    // Execute the tests!
    jasmineEnv.execute();
  </script>
  </body>

</html>


附: $onChange不是组件生命周期 Hook 的有效名称。它should be $onChanges .

关于angularjs - 如何测试父元素对组件绑定(bind)的更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48101798/

相关文章:

angularjs - 使用node.js和socket.io进行套接字身份验证(使用DaftMonk的generator-angular-fullstack)

javascript - Angular.js 第二个 Controller 不工作。

javascript - Angular - 在多个文件中定义模块

java - 如何在另一个测试类中重用现有的 JUnit 测试?

unit-testing - Visual Studio 中用于 Angular 2 开发的单元测试 Typescript

node.js - Angular 和 Sails 路由配置

java - 集成测试对单元测试的依赖性 : A Nice/Deadly Unimplementable Strategy for Building JUnit Integration Tests?

laravel - 单元测试、集成测试还是功能测试?

Angular 单元测试 router.events

angularjs - 为什么我的 AngularJS、Karma/Jasmine 测试运行如此缓慢?