aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/guide/dev_guide.unit-testing.ngdoc
diff options
context:
space:
mode:
authorIgor Minar2011-06-06 08:50:35 -0700
committerIgor Minar2011-06-06 22:52:02 -0700
commit7f1e2e48467f80cc083d24b44f088620e4e7bcb6 (patch)
tree731a91366c5780985be6d4c5ddbe34e307d5cb70 /docs/content/guide/dev_guide.unit-testing.ngdoc
parent5533e48dead5cff3107e72ee80bf0f19df77c1e9 (diff)
downloadangular.js-7f1e2e48467f80cc083d24b44f088620e4e7bcb6.tar.bz2
new batch of docs
Diffstat (limited to 'docs/content/guide/dev_guide.unit-testing.ngdoc')
-rw-r--r--docs/content/guide/dev_guide.unit-testing.ngdoc323
1 files changed, 323 insertions, 0 deletions
diff --git a/docs/content/guide/dev_guide.unit-testing.ngdoc b/docs/content/guide/dev_guide.unit-testing.ngdoc
new file mode 100644
index 00000000..d26a904e
--- /dev/null
+++ b/docs/content/guide/dev_guide.unit-testing.ngdoc
@@ -0,0 +1,323 @@
+@workInProgress
+@ngdoc overview
+@name Developer Guide: Unit Testing
+@description
+
+
+JavaScript is a dynamically typed language which comes with great power of expression, but it also
+come 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 do it.
+# 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 the question: Did I think about the logic correctly. Does the sort function order the list
+in the right order. In order to answer such question it is very important that we can isolate it.
+That is because when we are testing the sort function we don't want to be forced into crating
+related pieces such as the DOM elements, or making any XHR calls in getting the data to sort. While
+this may seem obvious it usually is very difficult to be able to call an individual function on a
+typical project. The reason is that the developers often time mix concerns, and they end up with a
+piece of code which does everything. It reads the data from XHR, it sorts it 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 crated abstraction 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 XHR, or create the right kind of DOM, or assert that your function has mutated the
+DOM in the right way. 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, which means if
+you don't follow these, you may very well end up with an untestable application.
+
+
+## Dependency Inject
+There are several ways in which you can get a hold of a dependency:
+1. You could create it using the `new` operator.
+2. You could look for it in a well know place, also known as global singleton.
+3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
+the registry? Must likely by looking it up in a well know place. See #2)
+4. You could expect that the it be handed to you.
+
+
+Out of the list above only the last of is testable. Lets look at why:
+
+
+### Using the `new` operator
+
+
+While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
+on a constructor permanently binds the call site to the type. For example lets say that we are
+trying to instantiate an `XHR` so that we can get some data from the server.
+
+
+<pre>
+function MyClass(){
+ this.doWork = function(){
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function(){...}
+ xhr.send();
+ }
+}
+</pre>
+
+
+The issue becomes, that in tests, we would very much like to instantiate a `MockXHR` which would
+allow us to return fake data and simulate network failures. By calling `new XHR()` we are
+permanently bound to the actual one, and there is no good way to replace it. Yes there is monkey
+patching, that is a bad idea for many reasons, which is outside the scope of this document.
+
+
+The class above is hard to test since we have to resort to monkey patching:
+<pre>
+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
+</pre>
+
+
+
+
+### Global look-up:
+Another way to approach the problem is look for the service in a well known location.
+
+
+<pre>
+function MyClass(){
+ this.doWork = function(){
+ global.xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+ }
+}
+</pre>
+
+
+While no new instance of dependency is being created, it is fundamentally the same as `new`, in
+that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
+through monkey patching. The basic issue for testing is that global variable needs to be mutated in
+order to replace it with call to a mock method. For further explanation why this is bad see: {@link
+http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
+State & Singletons}
+
+
+The class above is hard to test since we have to change global state:
+<pre>
+var oldXHR = glabal.xhr;
+glabal.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
+</pre>
+
+
+
+
+### Service Registry:
+
+
+It may seem as that this can be solved by having a registry for all of the services, and then
+having the tests replace the services as needed.
+
+
+<pre>
+function MyClass() {
+ var serviceRegistry = ????;
+ this.doWork = function(){
+ var xhr = serviceRegistry.get('xhr');
+ xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+}
+</pre>
+
+
+However, where dose the serviceRegistry come from? if it is:
+* `new`-ed up, the the test has no chance to reset the services for testing
+* global look-up, then the service returned is global as well (but resetting is easier, since
+there is only one global variable to be reset).
+
+
+The class above is hard to test since we have to change global state:
+<pre>
+var oldServiceLocator = glabal.serviceLocator;
+glabal.serviceLocator.set('xhr', function mockXHR(){});
+var myClass = new MyClass();
+myClass.doWork();
+// assert that mockXHR got called with the right arguments
+glabal.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
+</pre>
+
+
+
+
+### Passing in Dependencies:
+Lastly the dependency can be passed in.
+
+
+<pre>
+function MyClass(xhr) {
+ this.doWork = function(){
+ xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+}
+</pre>
+
+
+This is the proferred way since the code makes no assumptions as to where the `xhr` comes from,
+rather that who-ever crated the class was responsible for passing it in. Since the creator of the
+class should be different code the the user of the class, it separates the responsibility of
+creation from the logic, and that is what dependency-injection is in a nutshell.
+
+
+The class above is very testable, since in the test we can write:
+<pre>
+function xhrMock(args) {...}
+var myClass = new MyClass(xhrMock);
+myClass.doWork();
+// assert that xhrMock got called with the right arguments
+</pre>
+
+
+Notice that no global variables were harmed in the writing of this test.
+
+
+Angular comes with {@link dev_guide.di dependency-injection} built in which makes the right thin
+the easy thing 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, which is what we would like to test. If the logic
+for your application is mixed in with DOM manipulation, it will be hard to test as in the example
+below:
+
+
+<pre>
+function PasswordController(){
+ // 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);
+ }
+}
+</pre>
+
+
+The code above is problematic from testability, since it requires your test to have the right kind
+of DOM present when the code executes. The test would look like this:
+
+
+<pre>
+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('');
+</pre>
+
+
+In angular the controllers are strictly separated from the DOM manipulation logic which results in
+a much easier testability story as can be seen in this example:
+
+
+<pre>
+function PasswordCntrl(){
+ this.password = '';
+ this.grade = function(){
+ var size = this.password.length;
+ if (size > 8) {
+ this.strength = 'strong';
+ } else if (size > 3) {
+ this.strength = 'medium';
+ } else {
+ this.strength = 'weak';
+ }
+ };
+}
+</pre>
+
+
+and the tests is straight forward
+
+
+<pre>
+var pc = new PasswordController();
+pc.password('abc');
+pc.grade();
+expect(span.strength).toEqual('weak');
+</pre>
+
+
+Notice that the test is not only much shorter but it is easier to follow what is going on. We say
+that such a test tells a story, rather then asserting random bits which don't seem to be related.
+
+
+
+
+## Filters
+{@link api/angular.filter Filters} are functions which transform the data into user readable
+format. They are important because they remove the formatting responsibility from the application
+logic, further simplifying the application logic.
+
+
+<pre>
+angular.filter('length', function(text){
+ return (''+(text||'')).length;
+});
+
+
+var length = angular.filter('length');
+expect(length(null)).toEqual(0);
+expect(length('abc')).toEqual(3);
+</pre>
+
+
+## Directives
+Directives in angular are responsible for updating the DOM when the state of the model changes.
+
+
+
+
+## Mocks
+oue
+## Global State Isolation
+oue
+# Preferred way of Testing
+uo
+## JavaScriptTestDriver
+ou
+## Jasmine
+ou
+## Sample project
+uoe