2011-06-06 15:50:35 +00:00
|
|
|
@ngdoc overview
|
|
|
|
|
@name Developer Guide: Unit Testing
|
|
|
|
|
@description
|
|
|
|
|
|
|
|
|
|
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
2013-06-03 17:05:17 +00:00
|
|
|
comes with almost no help from the compiler. For this reason we feel very strongly that any code
|
2011-06-06 15:50:35 +00:00
|
|
|
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
2012-09-26 13:30:55 +00:00
|
|
|
Angular which makes testing your Angular applications easy. So there is no excuse for not testing.
|
|
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
# It is all about NOT mixing concerns
|
2012-09-26 13:30:55 +00:00
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
Unit testing as the name implies is about testing individual units of code. Unit tests try to
|
2012-09-26 13:30:55 +00:00
|
|
|
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
|
|
|
|
|
in the right order?"
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
In order to answer such a question it is very important that we can isolate the unit of code under test.
|
2012-09-26 13:30:55 +00:00
|
|
|
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.
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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
|
2012-09-26 13:30:55 +00:00
|
|
|
manipulates the DOM.
|
|
|
|
|
|
|
|
|
|
With Angular we try to make it easy for you to do the right thing, and so we
|
2013-11-20 18:11:51 +00:00
|
|
|
provide dependency injection for your XHR (which you can mock out) and we created abstractions which
|
2011-06-06 15:50:35 +00:00
|
|
|
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
|
2013-11-20 18:11:51 +00:00
|
|
|
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.
|
2012-09-26 13:30:55 +00:00
|
|
|
|
|
|
|
|
## With great power comes great responsibility
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2012-09-26 13:30:55 +00:00
|
|
|
## Dependency Injection
|
2013-11-20 18:11:51 +00:00
|
|
|
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.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2013-01-14 00:21:04 +00:00
|
|
|
Out of the four options in the list above, only the last one is testable. Let's look at why:
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
### Using the `new` operator
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<pre>
|
2011-10-07 18:27:49 +00:00
|
|
|
function MyClass() {
|
|
|
|
|
this.doWork = function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
var xhr = new XHR();
|
|
|
|
|
xhr.open(method, url, true);
|
2011-10-07 18:27:49 +00:00
|
|
|
xhr.onreadystatechange = function() {...}
|
2011-06-06 15:50:35 +00:00
|
|
|
xhr.send();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
|
2011-06-06 15:50:35 +00:00
|
|
|
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
|
2013-11-20 18:11:51 +00:00
|
|
|
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.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
|
2011-06-06 15:50:35 +00:00
|
|
|
<pre>
|
|
|
|
|
var oldXHR = XHR;
|
2011-10-07 18:27:49 +00:00
|
|
|
XHR = function MockXHR() {};
|
2011-06-06 15:50:35 +00:00
|
|
|
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:
|
2013-11-20 18:11:51 +00:00
|
|
|
Another way to approach the problem is to look for the service in a well-known location.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<pre>
|
2011-10-07 18:27:49 +00:00
|
|
|
function MyClass() {
|
|
|
|
|
this.doWork = function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
global.xhr({
|
|
|
|
|
method:'...',
|
|
|
|
|
url:'...',
|
|
|
|
|
complete:function(response){ ... }
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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
|
2013-03-30 12:51:28 +00:00
|
|
|
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
|
2013-11-20 18:11:51 +00:00
|
|
|
order to replace it with call to a mock method. For further explanation of why this is bad see: {@link
|
2011-06-06 15:50:35 +00:00
|
|
|
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
|
|
|
|
|
State & Singletons}
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
The class above is hard to test since we have to change the global state:
|
2011-06-06 15:50:35 +00:00
|
|
|
<pre>
|
2012-03-22 19:38:03 +00:00
|
|
|
var oldXHR = global.xhr;
|
|
|
|
|
global.xhr = function mockXHR() {};
|
2011-06-06 15:50:35 +00:00
|
|
|
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:
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
It may seem that this can be solved by having a registry of all the services and then
|
2011-06-06 15:50:35 +00:00
|
|
|
having the tests replace the services as needed.
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
function MyClass() {
|
|
|
|
|
var serviceRegistry = ????;
|
2011-10-07 18:27:49 +00:00
|
|
|
this.doWork = function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
var xhr = serviceRegistry.get('xhr');
|
|
|
|
|
xhr({
|
|
|
|
|
method:'...',
|
|
|
|
|
url:'...',
|
|
|
|
|
complete:function(response){ ... }
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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).
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
The class above is hard to test since we have to change the global state:
|
2011-06-06 15:50:35 +00:00
|
|
|
<pre>
|
2012-03-22 19:38:03 +00:00
|
|
|
var oldServiceLocator = global.serviceLocator;
|
|
|
|
|
global.serviceLocator.set('xhr', function mockXHR() {});
|
2011-06-06 15:50:35 +00:00
|
|
|
var myClass = new MyClass();
|
|
|
|
|
myClass.doWork();
|
|
|
|
|
// assert that mockXHR got called with the right arguments
|
2012-03-22 19:38:03 +00:00
|
|
|
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
2011-06-06 15:50:35 +00:00
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Passing in Dependencies:
|
2013-11-20 18:11:51 +00:00
|
|
|
Last, the dependency can be passed in.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
function MyClass(xhr) {
|
2011-10-07 18:27:49 +00:00
|
|
|
this.doWork = function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
xhr({
|
|
|
|
|
method:'...',
|
|
|
|
|
url:'...',
|
|
|
|
|
complete:function(response){ ... }
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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
|
2011-06-16 05:32:24 +00:00
|
|
|
class should be different code than the user of the class, it separates the responsibility of
|
2013-11-20 18:11:51 +00:00
|
|
|
creation from the logic. This is dependency-injection is in a nutshell.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
The class above is testable, since in the test we can write:
|
2011-06-06 15:50:35 +00:00
|
|
|
<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.
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
Angular comes with {@link di dependency injection} built-in, making the right thing
|
2011-06-16 05:32:24 +00:00
|
|
|
easy to do, but you still need to do it if you wish to take advantage of the testability story.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
## Controllers
|
2013-11-20 18:11:51 +00:00
|
|
|
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
|
2011-06-06 15:50:35 +00:00
|
|
|
below:
|
|
|
|
|
|
|
|
|
|
<pre>
|
2013-01-02 19:58:27 +00:00
|
|
|
function PasswordCtrl() {
|
2011-06-06 15:50:35 +00:00
|
|
|
// get references to DOM elements
|
|
|
|
|
var msg = $('.ex1 span');
|
|
|
|
|
var input = $('.ex1 input');
|
|
|
|
|
var strength;
|
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
this.grade = function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
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>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
The code above is problematic from a testability point of view since it requires your test to have the right kind
|
2011-06-06 15:50:35 +00:00
|
|
|
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);
|
2013-01-02 19:58:27 +00:00
|
|
|
var pc = new PasswordCtrl();
|
2011-06-06 15:50:35 +00:00
|
|
|
input.val('abc');
|
|
|
|
|
pc.grade();
|
|
|
|
|
expect(span.text()).toEqual('weak');
|
|
|
|
|
$('body').html('');
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
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:
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<pre>
|
2013-01-02 19:58:27 +00:00
|
|
|
function PasswordCtrl($scope) {
|
2012-06-10 16:01:42 +00:00
|
|
|
$scope.password = '';
|
|
|
|
|
$scope.grade = function() {
|
|
|
|
|
var size = $scope.password.length;
|
2011-06-06 15:50:35 +00:00
|
|
|
if (size > 8) {
|
2012-06-10 16:01:42 +00:00
|
|
|
$scope.strength = 'strong';
|
2011-06-06 15:50:35 +00:00
|
|
|
} else if (size > 3) {
|
2012-06-10 16:01:42 +00:00
|
|
|
$scope.strength = 'medium';
|
2011-06-06 15:50:35 +00:00
|
|
|
} else {
|
2012-06-10 16:01:42 +00:00
|
|
|
$scope.strength = 'weak';
|
2011-06-06 15:50:35 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
and the test is straight forward:
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<pre>
|
2013-06-04 21:10:04 +00:00
|
|
|
var $scope = {};
|
|
|
|
|
var pc = $controller('PasswordCtrl', { $scope: $scope });
|
|
|
|
|
$scope.password = 'abc';
|
|
|
|
|
$scope.grade();
|
|
|
|
|
expect($scope.strength).toEqual('weak');
|
2011-06-06 15:50:35 +00:00
|
|
|
</pre>
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
|
2011-06-06 15:50:35 +00:00
|
|
|
that such a test tells a story, rather then asserting random bits which don't seem to be related.
|
|
|
|
|
|
|
|
|
|
## Filters
|
2013-11-20 18:11:51 +00:00
|
|
|
{@link api/ng.$filterProvider Filters} are functions which transform the data into a user readable
|
2011-06-06 15:50:35 +00:00
|
|
|
format. They are important because they remove the formatting responsibility from the application
|
|
|
|
|
logic, further simplifying the application logic.
|
|
|
|
|
|
|
|
|
|
<pre>
|
2012-01-16 07:28:10 +00:00
|
|
|
myModule.filter('length', function() {
|
|
|
|
|
return function(text){
|
|
|
|
|
return (''+(text||'')).length;
|
|
|
|
|
}
|
2011-06-06 15:50:35 +00:00
|
|
|
});
|
|
|
|
|
|
2012-01-16 07:28:10 +00:00
|
|
|
var length = $filter('length');
|
2011-06-06 15:50:35 +00:00
|
|
|
expect(length(null)).toEqual(0);
|
|
|
|
|
expect(length('abc')).toEqual(3);
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
## Directives
|
2013-04-23 12:00:05 +00:00
|
|
|
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
|
|
|
|
|
|
2013-11-20 18:11:51 +00:00
|
|
|
Let's start with an angular app with no dependencies.
|
2013-04-23 12:00:05 +00:00
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
var app = angular.module('myApp', []);
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
Now we can add a directive to our app.
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
app.directive('aGreatEye', function () {
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'E',
|
|
|
|
|
replace: true,
|
2013-05-22 18:47:17 +00:00
|
|
|
template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
|
2013-04-23 12:00:05 +00:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
This directive is used as a tag `<a-great-eye></a-great-eye>`. It replaces the entire tag with the
|
2013-05-22 18:47:17 +00:00
|
|
|
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.
|
2013-04-23 12:00:05 +00:00
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
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);
|
2013-05-22 18:47:17 +00:00
|
|
|
// fire all the watches, so the scope expression {{1 + 1}} will be evaluated
|
|
|
|
|
$rootScope.$digest();
|
2013-04-23 12:00:05 +00:00
|
|
|
// Check that the compiled element contains the templated content
|
2013-05-22 18:47:17 +00:00
|
|
|
expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
|
2013-04-23 12:00:05 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
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
|
2013-05-22 18:47:17 +00:00
|
|
|
replaced the content and "lidless, wreathed in flame, 2 times" is present.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
2012-09-26 13:30:55 +00:00
|
|
|
|
2011-06-06 15:50:35 +00:00
|
|
|
## Sample project
|
2013-05-18 21:17:15 +00:00
|
|
|
See the {@link https://github.com/angular/angular-seed angular-seed} project for an example.
|
2013-05-16 15:11:37 +00:00
|
|
|
|