Method for unit testing in the JavaScript AngularJS library _ AngularJS

Source: Internet
Author: User
Tags define local autowatch
This article describes how to perform unit tests in the AngularJS library of JavaScript. It is mainly related to controllers in AngularJS, if you need it, you can refer to all developers who agree that unit testing is very beneficial in development projects. They help you ensure the quality of your code, so as to ensure more stable R & D, even if you need to refactor it.

Test-driven development Flowchart

AngularJS code claims that its high testability is indeed reasonable. The document lists end-to-end test instances. For projects like AngularJS, although unit testing is simple, it is not easy to do well. Even if detailed examples are provided in the official documentation, they are still very challenging in my practical application. Here is a simple example of how I operate it.

Instant Karma

Karma is a testing framework developed by Angular for JavaScript. It conveniently implements automatic execution of test tasks and replaces tedious manual operations (such as regression test set or loading the dependencies of the target test) the collaboration between Karma and Angular is like peanut butter and jelly.

You only need to define the configuration file in Karma to start it, and then it will automatically execute the test case in the expected test environment. You can create a test environment in the configuration file. Angular-seed is a fast implementation solution that I strongly recommend. In my recent project, Karma is configured as follows:

module.exports = function(config) {  config.set({    basePath: '../',     files: [      'app/lib/angular/angular.js',      'app/lib/angular/angular-*.js',      'app/js/**/*.js',      'test/lib/recaptcha/recaptcha_ajax.js',      'test/lib/angular/angular-mocks.js',      'test/unit/**/*.js'    ],     exclude: [      'app/lib/angular/angular-loader.js',      'app/lib/angular/*.min.js',      'app/lib/angular/angular-scenario.js'    ],     autoWatch: true,     frameworks: ['jasmine'],     browsers: ['PhantomJS'],     plugins: [      'karma-junit-reporter',      'karma-chrome-launcher',      'karma-firefox-launcher',      'karma-jasmine',      'karma-phantomjs-launcher'    ],     junitReporter: {      outputFile: 'test_out/unit.xml',      suite: 'unit'    }   })}


This is similar to the default configuration of angular-seed, except for the following differences:

  • You need to change the browser from Chrome to PhantomJS, so that you do not need to open a new browser window every time you jump, but there will be a window delay in The OSX system. So this plug-in and the browser settings have been changed.
  • Because my application needs to reference Google's Recaptcha service, the dependent recaptcha_ajax.js small file is added. This small configuration is as simple as adding a line of code in the Karma configuration file.

AutoWatch is indeed a cool setting that will allow Karma to automatically return to your test case when there is a file change. You can install Karma like this:

npm install karma

Angular-seed provides a simple script, inscripts/test. sh, to trigger Karma testing.


Design Test Cases with Jasmine

Most resources are available when the Jasmine-A behavior-driven development mode JavaScript testing framework is Angular designed for unit test cases.

This is what I will talk about next.

If you want to perform a unit test on AngularJS controller, you can use Angular dependency injection to import the service version required by the controller in the test scenario and check whether the expected results are correct. For example, I defined the tab that the controller needs to navigate to highlight:

app.controller('NavCtrl', function($scope, $location) {  $scope.isActive = function(route) {    return route === $location.path();  };})

What can I do if I want to test the isActive method? I will check whether the $ locationservice variable returns the expected value and the method returns the expected value. Therefore, in our test description, we define local variables to save the controlled version required during the test and inject it into the corresponding controller as needed. Then we add assertions in the actual test case to verify whether the actual results are correct. The process is as follows:

describe('NavCtrl', function() {  var $scope, $location, $rootScope, createController;   beforeEach(inject(function($injector) {    $location = $injector.get('$location');    $rootScope = $injector.get('$rootScope');    $scope = $rootScope.$new();     var $controller = $injector.get('$controller');     createController = function() {      return $controller('NavCtrl', {        '$scope': $scope      });    };  }));   it('should have a method to check if the path is active', function() {    var controller = createController();    $location.path('/about');    expect($location.path()).toBe('/about');    expect($scope.isActive('/about')).toBe(true);    expect($scope.isActive('/contact')).toBe(false);  });});

With the entire basic structure, you can design various types of tests. Because our test scenario uses a local environment to call the controller, you can add more attributes and then execute a method to clear these attributes, and then verify whether the attributes have been cleared.

$ HttpBackendIs Cool

What if you are calling the $ httpservice request or sending data to the server? Fortunately, Angular provides

$ HttpBackend mock method. In this way, you can customize the response content of the server, or make sure that the response results of the server are consistent with the expectations in the unit test.

The details are as follows:

describe('MainCtrl', function() {  var $scope, $rootScope, $httpBackend, $timeout, createController;  beforeEach(inject(function($injector) {    $timeout = $injector.get('$timeout');    $httpBackend = $injector.get('$httpBackend');    $rootScope = $injector.get('$rootScope');    $scope = $rootScope.$new();      var $controller = $injector.get('$controller');     createController = function() {      return $controller('MainCtrl', {        '$scope': $scope      });    };  }));   afterEach(function() {    $httpBackend.verifyNoOutstandingExpectation();    $httpBackend.verifyNoOutstandingRequest();  });   it('should run the Test to get the link data from the go backend', function() {    var controller = createController();    $scope.urlToScrape = 'success.com';     $httpBackend.expect('GET', '/slurp?urlToScrape=http:%2F%2Fsuccess.com')      .respond({        "success": true,        "links": ["http://www.google.com", "http://angularjs.org", "http://amazon.com"]      });     // have to use $apply to trigger the $digest which will    // take care of the HTTP request    $scope.$apply(function() {      $scope.runTest();    });     expect($scope.parseOriginalUrlStatus).toEqual('calling');     $httpBackend.flush();     expect($scope.retrievedUrls).toEqual(["http://www.google.com", "http://angularjs.org", "http://amazon.com"]);    expect($scope.parseOriginalUrlStatus).toEqual('waiting');    expect($scope.doneScrapingOriginalUrl).toEqual(true);  });});

As you can see, beforeEach call is actually quite similar. The only difference is that we get $ httpBackend from injector rather than directly. Even so, there are significant differences when creating different tests. For beginners, there will be an afterEachcall method to ensure that $ httpBackend does not have obvious abnormal requests after each use case execution. If you observe the settings of the test scenario and the application of the $ httpBackend method, you will find that the following points are not so intuitive.

In fact, calling the $ httpBackend method is simple and clear, but not enough-we have to pass the value to $ scope. $ apply encapsulate the call to $ scope in the actual test. runTest method. In this way, the HTTP request can be processed only after $ digest is triggered. As you can see, until we call $ httpBackend. $ httpBackend will be parsed after the flush () method, which ensures that we can verify whether the returned results are correct during the call process (in the above example, $ scope of controller. the parseOriginalUrlStatusproperty attribute will be passed to the caller, so we can monitor it in real time)

The following lines of code detect the assertions of the $ scopethat attribute during the call. Cool, right?

Tip: In some unit tests, users are used to marking a variable without a $ range. This is not mandatory or overemphasized in Angular documents, but I use $ scopelike in order to improve readability and consistency.

Conclusion

Maybe this is one of the things that I can do for others, but learning to write unit tests with Angular is really painful for me at the beginning. I found that my understanding of how to get started mostly comes from the patchwork of various blog articles and resources on the Internet. Instead of truly consistent or clear best practices, I chose them randomly. I want to provide some documents on my final results to help others who may still struggle. After all, they just want to write code, instead of having to understand all the weird features and unique usage of Angular and Jasmine. Therefore, I hope this article will be helpful to you.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.