[Unit Testing] AngularJS Unit Testing - Karma

本文介绍了如何使用Karma进行单元测试,包括安装工具、初始化配置、编写测试用例,以及如何针对AngularJS应用进行测试,涵盖AngularJS组件、指令、模板等的测试方法。同时,还涉及了测试速度优化、服务测试、控制器测试等内容。

Install Karam:

npm install -g karma
npm install -g karma-cli

 

Init Karam:

karma init

 

First test:

1. Add test file to the karma.conf.js:

    // list of files / patterns to load in the browser
    files: [
        'test/hello.js'
    ],

2. Add test file:

describe('First Unit testing with Karma', function() {
    it('sholud work', function() {
        expect(true).toBe(false);
    })
})

Of course, it will failed. 

Then we make it pass:

describe('First Unit testing with Karma', function() {
    it('sholud work', function() {
        expect(true).toBe(true);
    })
})

 

Testing with AngularJS


 

Install angular-mocks:

npm install angular-mocks

 

Include the angular.js and angular-mocks.js file before the test file:

    // list of files / patterns to load in the browser
    files: [
        'test/hello.js',
        'bower_components/angular/angular.js',
        'node_modules/angular-mocks/angular-mocks.js',
        'test/anuglarjs.js'
    ],

 

Write the test file:

describe('Testing with AngularJS', function() {
    //Want $scope and element can be used globally
    var $scope,
        element;

    //We need to inject $compile, $rootScope
    beforeEach(inject(function($compile, $rootScope) {
        $scope = $rootScope;
        //create an 'angular element'
        element = angular.element("<div>{{2+2}}</div>");
        //then compile it to real angular element
        //also it requires link function and scope
        element = $compile(element)($rootScope);
    }));

    it('should equals to 4', function() {
        //make sure to $digest() it to get the change
        $scope.$digest();
        expect(element.html()).toBe("5");  // change to "4" to make it pass
    })
})

Now, "4" is not "5", then it will failed, switch to "4" to make it pass.

 

Testing Directive:


 

    .directive('aGreatEye', function () {
        return {
            restrict: 'E',
            replace: true,   //Important to add replace: true, otherwise, it would be <h1 class="ng-binding">...</h1>"
            template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
        };
    });

 

describe('Testing directive', function() {
    var $scope,
        $compile,
        element;

    beforeEach(module('app'));

    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $scope = _$rootScope_;
    }));

    it("Replaces the element with the appropriate content", function() {
        element = $compile('<a-great-eye></a-great-eye>')($scope);
        $scope.$digest();
        expect(element.html()).toBe('lidless, wreathed in flame, 2 times');
    })
});

Underscore notation: The use of the underscore notation (e.g.: _$rootScope_) is a convention wide spread in AngularJS community to keep the variable names clean in your tests. That's why the $injector strips out the leading and the trailing underscores when matching the parameters. The underscore rule applies only if the name starts and ends with exactly one underscore, otherwise no replacing happens.

 

Testing Directives With External Templates:


 

1. Install 'karma-ng-html2js-preprocessor': https://github.com/karma-runner/karma-ng-html2js-preprocessor

npm install karma-ng-html2js-preprocessor --save-dev

 

2. Add config to karma.config.js:

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
        '*.html': ['ng-html2js']
    },

      ngHtml2JsPreprocessor: {
          // strip this from the file path
          stripPrefix: '/'  //because currently all the html files are located in / root
      },

 

3. Add html into beforeEach:

describe('Testing directive', function() {
    var $scope,
        $compile,
        element;

    beforeEach(module('app'));

    // Add html file here
    beforeEach(module('directivetest.html'));

    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $scope = _$rootScope_;
    }));

    it("Replaces the element with the appropriate content", function() {
        element = $compile('<a-great-eye></a-great-eye>')($scope);
        $scope.$digest();
        expect(element.html()).toBe('lidless, wreathed in flame, 2 times');
    })
});

 

    .directive('aGreatEye', function () {
        return {
            restrict: 'E',
            replace: true,
            templateUrl: 'directivetest.html'
        };
    });

   /*directivetest.html*/
   <h1>lidless, wreathed in flame, {{1 + 1}} times</h1>

 

Testing Directive Scope:


 

1. Check after a click event, scope value changed:

    .directive('aGreatEye', function () {

        return {
            restrict: 'EA',
            replace: true,
            templateUrl: 'directivetest.html',
            link: function(scope, element) {
                scope.isClicked = false;
                element.bind('click', function() {
                    scope.isClicked = true;
                })
            }
        };
    });

 

    it("isClicked is true after click", function() {
        element = $compile('<a-great-eye></a-great-eye>')($scope);
        $scope.$digest();
        element.triggerHandler('click');
        expect($scope.isClicked).toBe(true);
    });

 

2. Isolated scope testing:

    .directive('aGreatEye', function () {

        return {
            restrict: 'EA',
            replace: true,
            scope: {},
            templateUrl: 'directivetest.html',
            link: function(scope, element) {
                scope.isClicked = false;
                element.bind('click', function() {
                    scope.isClicked = true;
                })
            }
        };
    });

 

    it("isolated scope testing", function() {
        element.triggerHandler('click');
        expect(element.isolateScope().isClicked).toBe(true);
    });

 

You can no long use "element.scope()" or "$scope", you should use "element.isolateScope()".

 

Testing Directive Scope Binding:


 

Tow ways binding:

    .directive('aGreatEye', function () {

        return {
            restrict: 'EA',
            replace: true,
            scope: {
                flavor: "=" //Accpet an object
            },
            templateUrl: 'directivetest.html',
            link: function(scope, element) {

                element.bind('click', function() {
                    scope.isClicked = true;

                    scope.flavor.message += " World!";
                })
            }
        };
    });

 

    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $scope = _$rootScope_;
        $scope.goPro = {message: "Hello"};
        element = $compile('<div a-great-eye flavor="goPro"></div>')($scope);
        $scope.$digest();
    }));


    it("after click the scope.data should be Hello World!", function() {
        element.triggerHandler('click');
        expect($scope.goPro.message).toBe('Hello World!');
        expect(element.isolateScope().flavor.message).toBe("Hello World!");
    });

 

Testing Speed:


 Using ddescriber plugin to speed up unit testing.

Install the plugin "ddscriber for jasmine". Ctrl+Shift+D, to open the dailog. In the dialog you can choose which test to be included or not.

The code is modfied as: 

/**
 * Created by Answer1215 on 1/16/2015.
 */
ddescribe('Testing directive', function() {
    var $scope,
        $compile,
        element;

    beforeEach(module('app'));

    beforeEach(module('directivetest.html'));

    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $scope = _$rootScope_;
        $scope.goPro = {message: "Hello"};
        element = $compile('<div a-great-eye flavor="goPro"></div>')($scope);
        $scope.$digest();
    }));

    iit("Replaces the element with the appropriate content", function() {
        expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
    });

    //it("isClicked is true after click", function() {
    //    element.triggerHandler('click');
    //    expect($scope.isClicked).toBe(true);
    //});
    
    iit("isolated scope testing", function() {
        element.triggerHandler('click');
        //expect(element.isolateScope().isClicked).toBe(true);
    });


    xit("after click the scope.data should be Hello World!", function() {
        element.triggerHandler('click');
        expect($scope.goPro.message).toBe('Hello World!');
        //expect(element.isolateScope().flavor.message).toBe("Hello World!");
    });
});



xdescribe("error driective testing", function() {

    var $scope, $compile, element;

    beforeEach(module('app'));

    beforeEach(module('error.html'));

    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $scope = _$rootScope_;
        $compile = _$compile_;

        element = $compile('<error></error>')($scope);
        $scope.$digest();
    }));

    iit("Should contains alert-danger class", function() {
        expect(element.find('div').hasClass('alert-danger')).toBe(true);
    });
});

 

ddscribe / iit  -- include

xdscribe / xit -- exclude

 

Testing Service:


 Testing service, you need to inject the service into beforeEach.

ddescribe('testing service', function() {
    var SmithService;

    beforeEach(module('app'));
    beforeEach(inject(function(_SmithService_) {
        SmithService = _SmithService_;
    }));

    iit('should append Smith after each name', function() {
        expect(SmithService.getName("John")).toBe("John Smith");
    })
});

This testing case is test whenever use a service to get a name we will append " Smith" to the end.

 

  .service('SmithService', function() {
        var SmithService = {};
        SmithService.getName = function(name) {
            return name + " Smith";
        }

        return SmithService;
    });

 

Testing Controller:


 Testing controller, you need to inject the $controller into beforeEach. And get ctrl instence by using:

appCtrl = $controller("AppCtrl")

 

ddescribe('Test Controller', function() {
    var ctrl;
    beforeEach(module('app'));
    beforeEach(inject(function(_$controller_) {
        ctrl = _$controller_("TestCtrl");
    }));
    iit('Test Controller', function() {
        expect(ctrl.message).toBe("Hello");
    });
});

 

    .controller('TestCtrl', function() {
        var vm = this;

        vm.message = "Hello";
    });

 

<think> 我们正在讨论如何使用Karma和Mocha设置单元测试。根据提供的引用,我们可以总结如下: 引用[1]提到使用karma-webpack插件将Webpack和Karma直接集成,以便用Webpack预处理测试文件。 引用[2]展示了通过`karma init`命令初始化Karma配置文件的过程,其中选择了测试框架为Jasmine,但我们实际需要使用Mocha。 引用[3]展示了使用Karma+Mocha+Chai的示例,其中有一个测试用例写在test/index.js中。 因此,设置步骤包括: 1. 安装必要的包(Karma, Mocha, karma-mocha, karma-webpack等) 2. 初始化Karma配置文件(karma.conf.js)并配置使用Mocha 3. 配置Webpack(如果有的话) 4. 编写测试用例 5. 运行测试 下面我们详细说明: </think> ### 如何使用 Karma 和 Mocha 设置单元测试 以下是配置 Karma + Mocha 单元测试环境的完整步骤: #### 1. 安装依赖包 ```bash npm install --save-dev karma mocha karma-mocha karma-chai karma-webpack webpack karma-chrome-launcher ``` - `karma`: 测试运行器 - `mocha`: 测试框架 - `karma-mocha`: Mocha 的 Karma 适配器 - `karma-chai`: 断言库 - `karma-webpack`: Webpack 集成 - `karma-chrome-launcher`: 浏览器控制 #### 2. 初始化 Karma 配置 执行交互式配置向导: ```bash npx karma init ``` 按提示配置(参考引用[2]): ``` Testing framework: mocha # 选择 Mocha Require.js: no Browsers: Chrome # 或 PhantomJS Source/test files: src/**/*.spec.js # 测试文件路径 Exclude files: Watch files: yes ``` #### 3. 创建测试文件 (src/example.spec.js) ```javascript describe('基础测试套件', () => { it('应验证数字相加', () => { expect(1 + 1).to.equal(2); // 使用 Chai 断言 }); it('应检测空数组', () => { const arr = []; expect(arr).to.be.empty; // Chai 链式语法 }); }); ``` #### 4. 配置 karma.conf.js ```javascript module.exports = function(config) { config.set({ frameworks: ['mocha', 'chai'], // 添加 Mocha 和 Chai plugins: [ 'karma-mocha', 'karma-chai', 'karma-webpack', 'karma-chrome-launcher' ], files: [ 'src/**/*.spec.js' // 测试文件匹配模式 ], preprocessors: { '**/*.spec.js': ['webpack'] // 用 Webpack 预处理 }, webpack: { /* 可复用项目的 webpack 配置 */ }, browsers: ['ChromeHeadless'], // 无头浏览器模式 singleRun: true // 执行后退出 }); }; ``` #### 5. 添加测试脚本 (package.json) ```json "scripts": { "test": "karma start karma.conf.js" } ``` #### 6. 运行测试 ```bash npm test ``` 终端将显示测试结果: ``` Chrome Headless: Executed 2 of 2 SUCCESS ``` ### 关键配置说明 1. **框架集成**:通过 `karma-mocha` 适配器桥接 Mocha[^1] 2. **Webpack 预处理**:使用 `karma-webpack` 处理 ES6+ 语法和模块[^1] 3. **无头浏览器**:`ChromeHeadless` 无需可视化界面 4. **断言库**:Chai 提供 `expect/should/assert` 三种风格 > 注:若测试 Vue/React 组件,需额外安装 `@vue/test-utils` 或 `enzyme`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值