aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/guide/dev_guide.unit-testing.ngdoc
diff options
context:
space:
mode:
authorBrian Ford2014-03-03 12:35:00 -0800
committerBrian Ford2014-03-03 12:35:00 -0800
commitfde61423cf13cb57be02a6f79581cb96c34335f9 (patch)
tree7ac98e14d7ae67f2b69f76a2690d1e00459ca8c8 /docs/content/guide/dev_guide.unit-testing.ngdoc
parentc29d21f4cdc969991771d1e75c3a799eca7bf41a (diff)
downloadangular.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.ngdoc343
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.
-