Angularjs unit-testing (unit test) detailed _angularjs

Source: Internet
Author: User

JavaScript is a dynamic type language that gives her a strong ability to perform, but also makes it almost impossible for the compiler to provide any help to the developer. For this reason, we feel that writing any JavaScript code requires a powerful and complete test. Angular has many features that make it easier for us to test our applications. We should have no excuse not to write the test. )。

First, It is all about not mixing concerns (all about avoiding the complexity of code relationships ...). )

Unit tests, like names, are about testing a single "unit" of code. Unit tests try to answer these questions: Am I thinking logically right? is the sorting method the correct result? In order to answer these questions, it is particularly important to separate these issues. This is because when we are testing the sorting method, we do not want to care about other related fragments, such as DOM elements or initiating XHR requests to get data. Obviously, it is often difficult to call a function individually in a typical project. The reason for this is that developers often complicate relationships and ultimately make a piece of code seem to do everything. It gets the data through XHR, sorts the data, and then manipulates the DOM. Together with angular, we can write better code more easily, so angular provides us with xhr (we can simulate it) dependency injection, angular also creates abstractions that allow us to sort the model without having to manipulate the DOM. So, in the end, we can simply write a sort method and then create a collection of data from the test case to use for the Sort method test, and then determine if the result model is in line with expectations. The test does not need to wait for xhr, the person to create the corresponding DOM and the judgment function to manipulate the DOM correctly. Angular's core idea contains the testability of the code, but it also requires us to do the right thing. Angular is committed to simplifying the way things are done, but angular is not magic, which means that if we don't follow the following points, we may end up with an application that is not testable.

1. Dependency Inject

There are many ways to get dependent resources: 1 We can use the new operator, 2 we use a well-known way, known as "global Single Example"; 3) we can ask Registry service (but how do we get a registry?) You can look at the following chapters); 4 we can expect it to be passed over.

Of the methods listed above, only the last one is testable, let's see why:

1) Using The new operator

There is basically no error using the new operator, but the problem is that calling the constructor from new will permanently bind the caller to type. For example, we try to instantiate a Xhr object so that we can get some data from the server.

function MyClass () {
   this.dowork = function () {
     var xhr = new Xrh ();
     Xhr.open (method,url,true);
     Xhr.onreadystatechange = function () {...};
     Xhr.send ();
}
}

The problem is that when testing, we usually need to instantiate a virtual xhr that can return test data or network errors. By calling New XHR (), we bind the real XHR permanently, and there is no good way to replace it. There is, of course, a bad remedy, and there are plenty of reasons to think it's a bad idea:

var oldxhr = XHR;
XHR = new Mockxhr () {};
Myclass.dowork ();
Determine if MOCKXHR is invoked through normal parameters
XHR = oldxhr;//If you forget this step, it is easy to do something sad.

2) Global look-up

Another way to solve the problem is to get the resources that are dependent on a well-known place.

function MyClass () {
   this.dowork = function () {
      global.xhr ({...});}

Without creating an instance of a new dependent object, the problem is basically the same as new, and in addition to that sad patch, there is no good way to intercept the GLOBAL.XHR call while testing. The most fundamental problem with testing is that the global variable needs to be modified to invoke the virtual method instead. To learn more about its disadvantages, you can visit here: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/

The above code is more difficult to test, so we have to modify the global state:

var oldxhr = global.xhr;
GLOBAL.XHR = function mockxhr () {...};
var myClass = new MyClass ();
Determine if MOCKXHR is invoked through normal parameters
GLOBAL.XHR = oldxhr;//If you forget this step, it is easy to do something sad.

3) Service Registry

Having a registry with all the service seems to solve the problem, and then replace the required service in the test code.

function MyClass () {
   var serviceregistry =???;
   This.dowork = function () {
     var xhr = serviceregistry.get ("xhr");  ...
  };
}  

But where does the serviceregistry come from? If it is: * new-ed up, the "test has no chance to reset the" services for testing * Global look-up, then the service RET Urned is global as well (but resetting is easier, since there are only one global variable to be reset) (the following text is the same as garbled ...). I can't read it.)

According to this method, change the above class to the following way:

var oldservicelocator = global.servicelocator;
Global.serviceLocator.set (' xhr ', function mockxhr () {});
var myClass = new MyClass ();
Myclass.dowork ();
To determine whether MOCKXHR is invoked through normal parameters
global.servicelocator = oldservicelocator;//If you forget this step, it is easy to do something sad.

4) Passing in dependencies

Finally, dependent resources can be passed in.

function MyClass (XHR) {
   this.dowork = function () {
     xhr ({...})};
  }

This is the preferred way, because the code ignores where XHR comes from and doesn't care who created the xhr that came in. Therefore, the creator of the class and the user of the class can encode separately, which separates the responsibility for creation from the logic, which is an overview of dependency injection.

This class is easy to test, and in the test we can write this:

function Xhrmock (args) {...}
var myClass = new MyClass (xhrmock);
Myclass.dowrok ();
Make some judgments
... With this test code, we can realize that no global variables are corrupted.

Angular with dependency-injection (http://www.jb51.net/article/91775.htm), code written in this way, easier to write test code, if we want to write testable code, We'd better use it.

2. Controllers

Logic makes every application unique, and that's what we want to test. If our logic is mixed with DOM operations, it will be as difficult to test as the following example:

function Passwordcontroller () {
  //Get a reference to a DOM object
  var msg = $ ('. Ex1 span ');
  var input = $ ('. Ex1 input ');
  var strength;

  This.grade = function () {
     msg.removeclass (strength);
     var pwd = Input.val ();
     Password.text (PWD);
     if (Pwd.length > 8) {
        strength = ' strong ';
     } else if (Pwd.length > 3) {
        strength = ' Medium ';
     } E LSE {
        strength = ' weak ';
     }
    Msg.addclass (Strength). Text (strength);
  }


The above code encounters problems when testing, because it requires the correct DOM for our execution tests. The test code is as follows:

var input = $ (' <input type= "text"/> ');
var span = $ (' <span> ');
$ (' body '). HTML (' <div class= "Ex1" > "). Find (' div '). Append (Input). append (span);
var pc = new Passwordcontroller ();
Input.val (' abc ');
Pc.grade ();
Expect (Span.text ()). Toequal (' weak ');
$ (' body '). html (');

In angular, controller's strict separation of DOM operations logic will greatly reduce the difficulty of writing test cases, and look at the following examples:

function Passwordcntrl ($scope) {
  $scope. Password = ';
  $scope. Grade = function () {
     var size = $scope. password.length;
     if (Size > 8) {
          $scope. Strength = ' strong ';
     } else if (Size > 3) {
          $scope. Strength = ' Medium ';
     } els e {
          $scope. Strength = ' weak ';}}
  ;
}

The test code is straightforward:

var pc = new Passwordcontroller ($scope);
Pc.password (' abc ');
Pc.grade ();
Expect ($scope. Strength). Toequal (' weak ');

It is worth noting that the test code is not only more intermittent, but also easier to track. We've always said that test cases are telling stories, not other things that aren't relevant.

3. Filters

Filter (http://docs.angularjs.org/api/ng. $filter) is used to convert data to a user-friendly format. They are important because they separate the responsibility of the transformation format from the application logic, further simplifying the application logic.

Mymodule.filter (' Length ', function () {return
  function (text) {return
     (' + text| | '). length;
  }
});

var length = $filter (' length ');
Expect (length (null)). toequal (0);
Expect (length (' abc ')). Toequal (3);

4. Directives

5. Mocks

6. Global State Isolation

7. Preferred way of testing

8. Javascripttestdriver

9. Jasmine

Sample Project

Follow-up continue to update related articles, thank you for your support of this site!

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.