【问题标题】:Jasmine, Karma, Angular how to write test on my Angular app?Jasmine,Karma,Angular 如何在我的 Angular 应用程序上编写测试?
【发布时间】:2016-08-23 11:53:51
【问题描述】:

我刚刚跳到另一个项目,基本上,我被要求编写单元测试。由于我已经知道 Protractor 进行 e2e 测试,所以我现在切换到 Karma 和 Jasmine 进行单元测试。我已经下载了 karma、jasmine、karma-jasmine 和 karma-chrome-launcher。我也安装了 angular-mocks,所以我应该准备好开始了。我在互联网上阅读了很多东西,但现在,我真正需要的是一个真实应用程序的具体示例,以弄清楚如何开始编写测试。我不需要简单的例子,而是具体的例子和完整的解释。书籍和有用的链接也很受欢迎。提前感谢您的帮助/时间。

【问题讨论】:

标签: angularjs unit-testing jasmine karma-runner


【解决方案1】:
describe('ServiceBeingTested Name', (): void => {

var mockFirstDependency;
var mockSecondDependency;
var TestedService;

//Mock all dependencies
beforeEach((): void => {

    angular.mock.module('moduleServiceIsIn'); //Register the module which the service is in

    mockFirstDependency = sinon.stub(new MockFirstDependency());//Sinon if useful for mocking
    mockSecondDependency = sinon.stub(new MockSecondDependency());

    angular.mock.module(($provide): void => {
        $provide.value('FirstDependency', mockFirstDependency);
        $provide.value('SecondDependency', mockSecondDependency);
    });
});

beforeEach(inject(
    ['TestedService', (_TestedService_: TestedService): void => {
        TestedService = _TestedService_;
    }]));

//Describe each method in the service
describe('method to test', (): void => {

    it("should...", () => {
        //testing goes in here
        expect(TestedService.someMethod()).toBe("some value");
    });
});

这是一个如何测试 Angular 服务的简单示例。在这种情况下,该服务称为 TestedService。

首先你会看到三个变量声明。声明前两个以模拟此服务的两个依赖项。(假设此服务有两个依赖项)。最后一个变量声明将分配给正在测试的实际服务。

现在在 beforeEach 中:

angular.mock.module

这一行注册了你正在测试的服务所在的模块。这一行非常重要。

接下来的两行使用 Sinon.js 模拟正在测试的服务的依赖关系。我建议查看 Sinon.js

它的工作方式是我们有一个名为“FirstDependency”的依赖项,我创建了一个存根并称为“MockedFirstDependency”,在这里我创建了它的一个实例。

现在是下一部分(包含 $provide 的部分)

$provide.value('FirstDependency', mockFirstDependency);

上面这行代码的作用是告诉 Angular 每次使用 FirstDependency 服务时,都应该使用 mockFirstDependency。

现在在下一个之前,我所做的就是注入我正在测试的实际服务并将其分配给我的全局变量。

然后开始测试

编辑:测试控制器

describe('mainCtrl', (): void => {
    var $controllerConstructor;
    var MainCtrlInstance;
    var mockScope;
    var mockState;
    var mockStates;
    var mockGlobalData;

    beforeEach(() => {
        angular.mock.module('mainCtrlModule');

        mockScope = sinon.stub(new MockScope());
        mockState = sinon.stub(new MockState());
        mockStates = sinon.stub(new MockState());
        mockGlobalData = sinon.stub(new MockGlobalData());

        inject(($controller: ng.IControllerService): void => {
            $controllerConstructor = $controller;
        });

        //Constructs the controller, all dependencies must be injected here
        MainCtrlInstance = $controllerConstructor('mainCtrl',
            {
                '$Scope': mockScope,
                '$State': mockState,
                'States': mockStates,
                'srvGlobalData': mockGlobalData
            }
        );
    });

    describe('Method to Tests', (): void => {

        it("should...", (): void => {
            //Testing Begins
            expect(MainCtrlInstance.method()).toBe("some value");
        });
    });
});

编辑:测试指令

首先,您需要使用以下命令安装 Html2JsPreprocessor:npm install karma-ng-html2js-preprocessor --save-dev,如 here 所述。

karma.conf.js

files: [
    //Obviously include all of your Angular files
    //but make sure to include your jQuery before angular.js

    "directory/to/html/directive.html", // include html for directive
    "directive.js" // file directive is contained in
    "directive.spec.js"" // spec file
]

// include the directive html file to be preprocessed
preprocessors: {
    'directory/to/html/directive.html': 'ng-html2js'
},

plugins : [
    'karma-chrome-launcher',
    'karma-jasmine',
    'karma-ng-html2js-preprocessor' //include as a plugin too
],

ngHtml2JsPreprocessor: {
    //this part has a lot of useful features but unfortunately I
    //never got them to work, Google if you need help
},

directive.js

export class myDirectiveController {

    constructor(/*dependencies for controller*/) {
        //initializations
    }
    //other methods for directive class
}

export class myDirective implements ng.IDirective {
    constructor(/*dependencies for directive*/) { }
    static instance(/*dependencies*/): ng.IDirective {
        return new myDirective(/*dependencies for directive*/);
    }

    restrict = 'E';
    templateUrl = 'myDirective.html';
    controller = myDirectiveController;
    controllerAs = 'myDirectiveController';
    scope: {};
}

angular
.module('myDirectiveModule')
.directive('myDirective', myDirective.instance);

myDirective.spec.js

describe("myDirective", () => {

    //do you variable declarations but I'm leaving them out for simplicity

    beforeEach(() => {

        angular.mock.module(
            'myDirectiveModule', //and other modules in use
            'directory/to/html/directive.html'
            //include directive html as a module
        )

        // now do your mock dependencies as you did with services
        mockDependency = sinon.stub(new MockDependency());

        angular.mock.module(($provide): void => {
            $provide.value('dependency', mockDependency);
        }

        //inject $compile and $rootScope
        inject(($compile, $rootScope) => {

            scope = $rootScope.$new();

            // your directive gets compiled here
            element = angular.element("<my-directive></my-directive>");
            $compile(element)(scope);
            $rootScope.$digest();
            directiveController = element.controller('myDirective'); //this is your directive's name defined in .directive("myDirective", ...)
        });
    }

    describe("simple test", () => {

        it("should click a link", () => {

            var a = element.find("a");

            a.triggerHandler('click');

            //very important to call scope.$digest every you change anything in the view or the model
            scope.$digest();

            expect('whatever').toBe('whatever');
        });

    });
}

之前当我声明在 Angular 之前包含你的 jQuery 文件时,这样做是因为 angular.element() 会生成一个 jQuery 对象,你可以在该对象上使用 jQuery API,但如果你不首先包含 jQuery,那么你 angular.element() 将生成一个 jQuery 对象,您可以在该对象上使用 jQuery API。 element() 返回一个包含较少方法的 jQLite 对象。

调用 scope.$digest() 也很重要,因为它会更新指令的绑定。

【讨论】:

  • 一个有更多依赖的控制器呢?假设我有一个类似的模块
  • 一个有更多依赖的控制器呢?假设我有一个像这样的模块:' angular.module("main",[]).controller("mainCtrl", function ($scope, $state, States, srvGlobalData){ if (!srvGlobalData.userData.loggedUser) $state.go(States.login); }) .directive('main', function() { return { restrict: 'E', controller: 'mainCtrl', templateUrl: 'url...' } }); '
  • 你说 :" : void =>", "=>" 是什么意思???我不明白。提前致谢
  • @Mar3,抱歉,这是 TypeScript,JavaScript 等价物是... function () {} 例如。 ($compile) => {} 等价于 function($compile){} 以及关于void,这只是返回类型,可以忽略
  • 'describe('Test a controller', function(){ var $scope, ctrl, $state; //模拟服务 var StatesMock, srvGlobalDataMock; beforeEach(function() { module('main '); 注入(函数($rootScope, $controller, $state,srvGlobalData){ $scope = $rootScope.$new(); ctrl = $controller('geiatCtrl', { $scope: $scope, $state: $ state, States: geiatStatesMock, srvGlobaldata: srvGlobalDataMock}); }); }); it('应该加载', function () { expect(ctrl).toBeDefined(); }); });'
猜你喜欢
  • 1970-01-01
  • 2015-12-07
  • 1970-01-01
  • 1970-01-01
  • 2018-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多