zoukankan      html  css  js  c++  java
  • AngularJS单元测试系列-指令的单元测试

    指令的单元测试有几个关键的步骤,可以设置一个检查列表,然后看看每一条是否都执行了即可:
    (1)在单元测试中注入$compile服务
    (2)创建待测试的指令的作用域
    (3)单元测试中没有服务器,所以如果待测试的指令中用到templateUrl,需要在$httpBackend中加入期待返回的内容,用于模拟测试中的模板。
    (4)创建指令实例 —— HTML元素来触发待测试的指令(通过$compile服务和创建的作用域来编译HTML元素)
    (5)更新HTML中的数据绑定,加载并渲染$httpBackend中定义的HTML模板,以便下一步执行单元测试

    前5项适用于任何指令的单元测试,第6项涉及页面渲染和业务逻辑,不同的指令中各有不同:
    (6)为指令的渲染方式以及link函数中定义的函数设置预期值(expect)

    注释:单元测试中不会真的加载HTML,因为服务器不存在。

    以stockWidge指令为例:
    angular.module('stockMarketApp', [])
    .directive('stockWidge', [function() {
    return {
    templateUrl: 'stock.html', //渲染逻辑定义在stock.html中,通过templateUrl加载
    restrict: 'A', //指令只能通过属性的形式调用
    scope: { //有一个隔离作用域
    stockData: '=',
    stockTitle: '@',
    whenSelect: '&'
    },
    link: function(scope, elem, attrs) { //link函数中定义了两个函数
    scope.getChange = function(stock) {
    return Math.ceil( ((stock.price - stock.previous) / stock.previous) * 100);
    };

    scope.onSelect = function() {
    scope.whenSelect({
    stockName: scope.stockData.name,
    stockPrice: scope.stockData.price,
    stockPrevious: scope.stockData.previous
    });
    };
    }
    };
    }]);

    下面编写单元测试用例:

    <1>测试指令的渲染过程(重点是指令本身的作用域isolateScope/指令所对应的元素的父级作用域scope)
    describle('Stock Widget Directive Rendering', function() {
    beforeEach(module('stockMarketApp'));
    var compile, mockBackend, rootScope;

    //第一步:注入测试必要的用于创建和测试指令的服务
    beforeEach(inject(function($compile, $httpBackend, $rootScope) {
    compile: $compile, //用于创建指令的实例
    mockBackend: $httpBackend, //用于模拟和处理服务器请求以加载模板
    rootScope: $rootScope //用于创建指令的作用域
    }));

    it('should render HTML based on scope correctly', function() {
    //第二步:创建指令实例对应的作用域(element元素的父级作用域scope)并设置必要的变量
    var scope = rootScope.new();
    scope.myStock = {
    name: 'Best Stock',
    price: 100,
    previous: 200
    };
    scope.title = 'the best';

    //第三步:在$httpBackend中指定加载模板以及其内容的预期值(提供HTML以测试元素是否被正确渲染,数据是否准确)
    mockBackend.expectGET('stock.html').respond(
    '<div ng-bind="stockTitle"></div>' +
    '<div ng-bind="stockData.price"></div>'
    );

    //第四步:创建一个指令实例,编译触发指令的HTML,这会返回一个经过编译的函数,我们可以通过作用域来调用这个函数
    var element = compile(
    '<div stock-widget stock-data="myStock" stock-title="This is {{title}}"></div>'
    )(scope);

    //第五步:AngularJS更新HTML中的数据绑定,$httpBackend中定义的HTML会被加载并渲染出来,才能进一步做单元测试
    scope.digest(); //我们手动创建并编译了HTML,需要手动触发并通知AngularJS来用最新数据更新模板
    mockBackend.flush();

    //第六步:设置预期值并测试渲染结果
    expect(element.html).toEqual(
    '<div ng-bind="stockTitle" class="ng-binding">This is the best</div>' +
    '<div ng-bind="stockData.price" class="ng-binding">100</div>'
    );
    });
    });

    <2>测试指令的业务逻辑和行为
    describle('Stock Widget Directive Behavior', function(){
    beforeEach(module('stockMarketApp'));
    var compile, mockBackend, rootScope;

    //第一步:注入必要的服务
    beforeEach(inject(function($compile, $httpBackend, $rootScope){
    compile = $compile;
    mockBackend = $httpBackend;
    rootScope = $rootScope;
    }));

    it('should have functions and data on scope correctly', function(){
    //第二步:创建指令实例对应的作用域并设置必要的变量
    var scope = rootScope.$new();
    var scopeClickCalled = '';
    scope.myStock = {
    name: 'Best Stock',
    price: 100,
    previous: 200
    };
    scope.title = 'the best';
    scope.userClick = function(stockPrice, stockPrevious, stockName){
    scopeClickCalled = stockPrice + ';' + stockPrevious + ';' + stockName;
    };

    //第三步:模拟加载模板
    mockBackend.expectGET('stock.html').respond(
    '<div ng-bind="stockTitile"></div>' +
    '<div ng-bind="stockData.price"></div>'
    );

    //第四步:创建一个指令实例,编译触发指令的HTML
    var element = compile(
    '<div stock-widget stock-data="myStock" stock-title="This is {{title}}" whenSelect="userClick(stockPrice, stockPrevious, stockName)"></div>'
    )(scope);

    //第五步:更新数据绑定
    scope.$digest();
    mockBackend.flush();

    //第六步:设置预期值并测试指令中的函数
    //请求元素的隔离作用域,不同于element.scope —— 指令所对应元素的父作用域。而isolateScope是指令本身的作用域。
    var compiledElementScope = element.isolateScope();
    expect(compiledElementScope.stockData).toEqual(scope.stockData);
    expect(compiledElementScope.getChange(compiledElementScope.stockData)).toEqual(-50);

    expect(scopeClickCalled).toEqual('');
    compiledElementScope.onSelect();
    expect(scopeClickCalled).toEqual('100;200;Best Stock');
    });
    });

    其他
    (1)如何测试真正的HTML渲染?
    1、使用templateUrl时,不会真的去加载模板,我们可以使用测试模板来测试指令,如上面的例子中所示。
    2、使用template代替templateUrl,这样会将模板作为指令的一部分加载进来,但模板较大时会比较混乱。
    3、使用类似Grunt-HTML2JS工具,将接收的HTML模板转换成AngularJS的服务和工厂类,这样就可以将模板加载为服务并让它们能在单元测试中使用。
    (2)如何处理指令中的依赖?
    AngularJS会自动找出依赖并将它们自动注入。也可以将服务注入到单元测试中。

  • 相关阅读:
    web前端学习(四)JavaScript学习笔记部分(8)-- JavaScript瀑布流
    localStorage对象简单应用
    html文档加载顺序简单理解
    回调函数理解(转载)
    web前端学习(四)JavaScript学习笔记部分(8)-- JavaScript 浏览器对象
    web前端学习(四)JavaScript学习笔记部分(7)-- JavaScript DOM对象控制HTML元素详解
    JS random函数深入理解(转载)
    web前端学习(四)JavaScript学习笔记部分(6)-- js内置对象
    下雪了-js下雪效果
    LESS笔记
  • 原文地址:https://www.cnblogs.com/shiddong/p/6245306.html
Copyright © 2011-2022 走看看