diff options
| author | Brian Ford | 2014-03-03 12:35:00 -0800 | 
|---|---|---|
| committer | Brian Ford | 2014-03-03 12:35:00 -0800 | 
| commit | fde61423cf13cb57be02a6f79581cb96c34335f9 (patch) | |
| tree | 7ac98e14d7ae67f2b69f76a2690d1e00459ca8c8 /docs/content/guide/dev_guide.unit-testing.ngdoc | |
| parent | c29d21f4cdc969991771d1e75c3a799eca7bf41a (diff) | |
| download | angular.js-fde61423cf13cb57be02a6f79581cb96c34335f9.tar.bz2 | |
docs(guide/unit-testing): rename and fix formatting
Diffstat (limited to 'docs/content/guide/dev_guide.unit-testing.ngdoc')
| -rw-r--r-- | docs/content/guide/dev_guide.unit-testing.ngdoc | 343 | 
1 files changed, 0 insertions, 343 deletions
| diff --git a/docs/content/guide/dev_guide.unit-testing.ngdoc b/docs/content/guide/dev_guide.unit-testing.ngdoc deleted file mode 100644 index 2fba61a3..00000000 --- a/docs/content/guide/dev_guide.unit-testing.ngdoc +++ /dev/null @@ -1,343 +0,0 @@ -@ngdoc overview -@name  Unit Testing -@description - -JavaScript is a dynamically typed language which comes with great power of expression, but it also -comes with almost no help from the compiler. For this reason we feel very strongly that any code -written in JavaScript needs to come with a strong set of tests. We have built many features into -Angular which makes testing your Angular applications easy. So there is no excuse for not testing. - -# It is all about NOT mixing concerns - -Unit testing as the name implies is about testing individual units of code. Unit tests try to -answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list -in the right order?" - -In order to answer such a question it is very important that we can isolate the unit of code under test. -That is because when we are testing the sort function we don't want to be forced into creating -related pieces such as the DOM elements, or making any XHR calls in getting the data to sort. - -While this may seem obvious it can be very difficult to call an individual function on a -typical project. The reason is that the developers often mix concerns resulting in a -piece of code which does everything. It makes an XHR request, it sorts the response data and then it -manipulates the DOM. - -With Angular we try to make it easy for you to do the right thing, and so we -provide dependency injection for your XHR (which you can mock out) and we created abstractions which -allow you to sort your model without having to resort to manipulating the DOM. So that in the end, -it is easy to write a sort function which sorts some data, so that your test can create a data set, -apply the function, and assert that the resulting model is in the correct order. The test does not -have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your -function has mutated the DOM in the right way. - -## With great power comes great responsibility - -Angular is written with testability in mind, but it still requires that you do the right thing. -We tried to make the right thing easy, but Angular is not magic. If you don't follow these guidelines -you may very well end up with an untestable application. - -## Dependency Injection -There are several ways in which you can get a hold of a dependency. You can: -1. Create it using the `new` operator. -2. Look for it in a well-known place, also known as a global singleton. -3. Ask a registry (also known as service registry) for it. (But how do you get a hold of -the registry? Most likely by looking it up in a well known place. See #2.) -4. Expect it to be handed to you. - -Out of the four options in the list above, only the last one is testable. Let's look at why: - -### Using the `new` operator - -While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new` -on a constructor. This permanently binds the call site to the type. For example, lets say that we try to -instantiate an `XHR` that will retrieve data from the server. - -```js -function MyClass() { -  this.doWork = function() { -    var xhr = new XHR(); -    xhr.open(method, url, true); -    xhr.onreadystatechange = function() {...} -    xhr.send(); -  } -} -``` - -A problem surfaces in tests when we would like to instantiate a `MockXHR` that would -allow us to return fake data and simulate network failures. By calling `new XHR()` we are -permanently bound to the actual XHR and there is no  way to replace it. Yes, we could monkey -patch, but that is a bad idea for many reasons which are outside the scope of this document. - -Here's an example of how the class above becomes hard to test when resorting to monkey patching: - -```js -var oldXHR = XHR; -XHR = function MockXHR() {}; -var myClass = new MyClass(); -myClass.doWork(); -// assert that MockXHR got called with the right arguments -XHR = oldXHR; // if you forget this bad things will happen -``` - - -### Global look-up: -Another way to approach the problem is to look for the service in a well-known location. - -```js -function MyClass() { -  this.doWork = function() { -    global.xhr({ -      method:'...', -      url:'...', -      complete:function(response){ ... } -    }) -  } -} -``` - -While no new dependency instance is created, it is fundamentally the same as `new` in -that no  way exists to intercept the call to `global.xhr` for testing purposes, other then -through monkey patching. The basic issue for testing is that a global variable needs to be mutated in -order to replace it with call to a mock method. For further explanation of why this is bad see: [Brittle Global -State & Singletons](http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/) - -The class above is hard to test since we have to change the global state: - -```js -var oldXHR = global.xhr; -global.xhr = function mockXHR() {}; -var myClass = new MyClass(); -myClass.doWork(); -// assert that mockXHR got called with the right arguments -global.xhr = oldXHR; // if you forget this bad things will happen -``` - - -### Service Registry: - -It may seem that this can be solved by having a registry of all the services and then -having the tests replace the services as needed. - -```js -function MyClass() { -  var serviceRegistry = ????; -  this.doWork = function() { -    var xhr = serviceRegistry.get('xhr'); -    xhr({ -      method:'...', -      url:'...', -      complete:function(response){ ... } -    }) -} -``` - -However, where does the serviceRegistry come from? If it is: -* `new`-ed up, the test has no chance to reset the services for testing. -*  a global look-up then the service returned is global as well (but resetting is easier, since -only one global variable exists to be reset). - -The class above is hard to test since we have to change the global state: - -```js -var oldServiceLocator = global.serviceLocator; -global.serviceLocator.set('xhr', function mockXHR() {}); -var myClass = new MyClass(); -myClass.doWork(); -// assert that mockXHR got called with the right arguments -global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen -``` - - -### Passing in Dependencies: -Last, the dependency can be passed in. - -```js -function MyClass(xhr) { -  this.doWork = function() { -    xhr({ -      method:'...', -      url:'...', -      complete:function(response){ ... } -    }) -} -``` - -This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares -instead about whoever created the class  responsible for passing it in. Since the creator of the -class should be different code than the user of the class, it separates the responsibility of -creation from the logic. This is dependency-injection in a nutshell. - -The class above is testable, since in the test we can write: - -```js -function xhrMock(args) {...} -var myClass = new MyClass(xhrMock); -myClass.doWork(); -// assert that xhrMock got called with the right arguments -``` - -Notice that no global variables were harmed in the writing of this test. - -Angular comes with {@link di dependency injection} built-in, making the right thing -easy to do, but you still need to do it if you wish to take advantage of the testability story. - -## Controllers -What makes each application unique is its logic, and the logic is what we would like to test. If the logic -for your application contains DOM manipulation, it will be hard to test. See the example -below: - -```js -function PasswordCtrl() { -  // get references to DOM elements -  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'; -    } else { -      strength = 'weak'; -    } -    msg -     .addClass(strength) -     .text(strength); -  } -} -``` - -The code above is problematic from a testability point of view since it requires your test to have the right kind -of DOM present when the code executes. The test would look like this: - -```js -var input = $('<input type="text"/>'); -var span = $('<span>'); -$('body').html('<div class="ex1">') -  .find('div') -    .append(input) -    .append(span); -var pc = new PasswordCtrl(); -input.val('abc'); -pc.grade(); -expect(span.text()).toEqual('weak'); -$('body').empty(); -``` - -In angular the controllers are strictly separated from the DOM manipulation logic and this results in -a much easier testability story as the following example shows: - -```js -function PasswordCtrl($scope) { -  $scope.password = ''; -  $scope.grade = function() { -    var size = $scope.password.length; -    if (size > 8) { -      $scope.strength = 'strong'; -    } else if (size > 3) { -      $scope.strength = 'medium'; -    } else { -      $scope.strength = 'weak'; -    } -  }; -} -``` - -and the test is straight forward: - -```js -var $scope = {}; -var pc = $controller('PasswordCtrl', { $scope: $scope }); -$scope.password = 'abc'; -$scope.grade(); -expect($scope.strength).toEqual('weak'); -``` - -Notice that the test is not only much shorter, it is also easier to follow what is happening. We say -that such a test tells a story, rather then asserting random bits which don't seem to be related. - -## Filters -{@link ng.$filterProvider Filters} are functions which transform the data into a user readable -format. They are important because they remove the formatting responsibility from the application -logic, further simplifying the application logic. - -```js -myModule.filter('length', function() { -  return function(text){ -    return (''+(text||'')).length; -  } -}); - -var length = $filter('length'); -expect(length(null)).toEqual(0); -expect(length('abc')).toEqual(3); -``` - -## Directives -Directives in angular are responsible for encapsulating complex functionality within custom HTML tags, -attributes, classes or comments. Unit tests are very important for directives because the components -you create with directives may be used throughout your application and in many different contexts. - -### Simple HTML Element Directive - -Let's start with an angular app with no dependencies. - -```js -var app = angular.module('myApp', []); -``` - -Now we can add a directive to our app. - -```js -app.directive('aGreatEye', function () { -    return { -        restrict: 'E', -        replace:  true, -        template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>' -    }; -}); -``` - -This directive is used as a tag `<a-great-eye></a-great-eye>`. It replaces the entire tag with the -template `<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>`. Now we are going to write a jasmine unit test to -verify this functionality. Note that the expression `{{1 + 1}}` times will also be evaluated in the rendered content. - -```js -describe('Unit testing great quotes', function() { -    var $compile; -    var $rootScope; - -    // Load the myApp module, which contains the directive -    beforeEach(module('myApp')); - -    // Store references to $rootScope and $compile -    // so they are available to all tests in this describe block -    beforeEach(inject(function(_$compile_, _$rootScope_){ -      // The injector unwraps the underscores (_) from around the parameter names when matching -      $compile = _$compile_; -      $rootScope = _$rootScope_; -    })); -     -    it('Replaces the element with the appropriate content', function() { -        // Compile a piece of HTML containing the directive -        var element = $compile("<a-great-eye></a-great-eye>")($rootScope); -        // fire all the watches, so the scope expression {{1 + 1}} will be evaluated -        $rootScope.$digest(); -        // Check that the compiled element contains the templated content -        expect(element.html()).toContain("lidless, wreathed in flame, 2 times"); -    }); -}); -``` - -We inject the $compile service and $rootScope before each jasmine test. The $compile service is used -to render the aGreatEye directive. After rendering the directive we ensure that the directive has -replaced the content and "lidless, wreathed in flame, 2 times" is present. - - -## Sample project -See the [angular-seed](https://github.com/angular/angular-seed) project for an example. - | 
