A method of unit testing in JavaScript ANGULARJS libraries

Source: Internet
Author: User
Tags config flush http request min autowatch


This article mainly introduced in the JavaScript Angularjs Library to carry on the unit test method, mainly aims at the ANGULARJS in the controller correlation, needs the friend may refer to under



Developers agree that unit testing is good for development projects. They help you ensure the quality of your code, ensuring more stable research and development, and even more confidence when refactoring is needed.



Test-driven Development flowchart



Angularjs's code claims that its higher testability is indeed reasonable. This is illustrated by simply listing the End-to-end test instances in the document. Projects like ANGULARJS say unit testing is simple but it's not easy to do it well. Even though the official documents provide detailed examples, they are challenging in my practical application. Here I will simply demonstrate how I operate it.



Instant Karma



Karma is a test-run framework for the angular team to develop for JavaScript. It makes it easy to automate test tasks to replace tedious manual operations (like regression test sets or dependencies that load target tests) Karma and angular collaboration is like peanut butter and jelly.



You only need to define a profile in karma to start it, and then it will automate the test case in the desired test environment. You can set up the relevant test environment in the configuration file. Angular-seed is a highly recommended program that I can quickly implement. In my recent project, the Karma configuration is 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 don't have to open a new browser window every time you jump, but there is a window delay on OS X. So the plugin and browser settings have been changed.



Because my application needs to refer to Google's reCAPTCHA service, it adds a dependent recaptcha_ajax.js small file. This small configuration is as simple as adding one line of code to a Karma configuration file.



Autowatch is really a cool setup that lets karma automatically return to your test cases when there are file changes. You can install karma this way:






1NPM Install Karma



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



Designing test Cases with jasmine



When using jasmine----a JavaScript test framework for behavior-driven development patterns, most of the resources are available when designing unit test cases for angular.



That's what I'm going to talk about next.






If you want to do unit tests on ANGULARJS controller, you can take advantage of angular dependency Injection Dependency Injection feature to import the service version that controller needs in the test scenario and check that the expected results are correct. For example, I've defined this controller to highlight the page sign that needs to be navigated:






App.controller (' Navctrl ', function ($scope, $location) {$scope. isactive = function (route) {return route = = $location. PA Th (); }; })


What would I do if I wanted to test the IsActive method? I'll check whether the $locationservice variable returns the expected value, and whether the method returns the expected value. Therefore, in our test instructions we will define the local variables to save the controlled version needed during the test and inject them into the corresponding controller when needed. Then in the actual test case we will add an assertion to verify that the actual result is correct. The whole 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 to check if the path is active ', function () {var controller = Createcontroller (); $locati On.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. Since our test scenario uses a local environment to invoke controller, you can add a few more attributes and then perform a method to clear the properties, and then verify that the attribute is not cleared.



$httpBackendIs Cool



So what if you're calling $httpservice requests or sending data to the server? Fortunately, Angular provides a



A mock method $httpBackend. In this way, you can customize the response content of the server, or ensure that the server's response results are consistent with the expectations in the unit test.






Specific 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 really similar, the only difference is that we get the $httpbackend from the injector rather than get it directly. Even so, there are some obvious differences in creating different tests. For beginners, there is a Aftereachcall method to ensure that $httpbackend does not have a significant exception request after each use case is executed. If you look at the setting of the test scenario and the application of the $httpbackend method, you will find that a few points are not so intuitive.



Actually the method of calling $httpbackend is straightforward but not enough-we have to encapsulate the call into the $scope.runtest method in the actual test in the method of passing the value to $scope. $apply. This allows the HTTP request to be processed after the $digest is triggered. And as you can see, the $httpbackend will not be parsed until we call the $httpbackend.flush () method, which guarantees that we will be able to validate the return result in the call process (in the above example, the controller $ The Scope.parseoriginalurlstatusproperty attribute will be passed to the caller, so we can monitor it in real time.



The next few lines of code are assertions that detect the $scopethat property during the call process. Pretty cool, huh?



Tip: In some unit tests, users are accustomed to marking a range with no $ as a variable. This is not mandatory or overly emphasized in the angular document, but I use $scopelike this way to improve readability and consistency in my use.



Conclusion






Maybe that's one of the things I can do for other people, but learning to use angular to write unit tests is really painful for me at first. I find myself most of the understanding of how to begin with a patchwork of blog posts and resources on the Internet, with no real consistency or clear best practices, but by natural random choices. I want to provide some documentation for the results I've finally gotten to help others who may still be struggling in the pit, after all, they just want to write code, rather than having to understand all the weird features and unique uses of angular and jasmine. So I hope this article will help you a little bit.





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.