Jasmine, Karma, Angular how do I write a test in my Angular app?

I just switched to another project and basically I was asked to write unit tests. Since I already know Protractor for testing e2e, I have now switched to Karma and Jasmine for unit testing. I have already downloaded karma, jasmine, karma jasmine and karma chrome launcher. I have angular-mocks installed as well, so I should be ready to run. I have read a lot of things on the internet, but now I really need a concrete example of a real application to figure out how to start writing tests. I don't need simple examples, but specific examples and complete explanations. Books and helpful links are also welcome. Thanks in advance for your help / time.

+1


source to share


1 answer


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");
    });
});

      

This is a simple example of testing an angular service. In this case, the service is called TestedService.

The first thing you'll see are three variable declarations. The first two are declared to extract two dependencies for this service. (Let's assume this service has two dependencies). The last variable declaration will be assigned the actual service under test.

Now in beforeEach:

angular.mock.module

      

This line registers the module that contains the service under test. This line is very important.

The next two lines use Sinon.js to mock the dependencies of the service being checked. I recommend looking at Sinon.js

How it works, we have a dependency called "FirstDependency" in which I created a stub and named "MockedFirstDependency" and here I created an instance of that file.

Now for the next part, which (the part that includes the $ provide)

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

      

As the above line shows, it tells angular to use mockFirstDependency instead whenever the FirstDependency service is used.

Now, in the next beforeEach, all I do is insert the actual service I'm testing and assign it to my global variable.

Then let's start testing



EDIT: Controllers

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");
        });
    });
});

      

EDIT: Testing directives

First you need to install Html2JsPreprocessor with this command: npm install karma-ng-html2js-preprocessor --save-dev as mentioned.

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 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');
        });

    });
}

      

Earlier, when I stated that you included your jQuery file before Angular, do so because angular.element () will create a jQuery object that you can use the jQuery API on, but if you don't include jQuery in the first place then you are angular. element () returns a jQLite object with fewer methods.

It's also important to call scope. $ digest () as this updates the bindings for your directive.

0


source







All Articles