Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 0aa560e

Browse files
committed
move the DI stuff
1 parent 0ce7034 commit 0aa560e

File tree

1 file changed

+5
-141
lines changed

1 file changed

+5
-141
lines changed

docs/content/guide/unit-testing.ngdoc

+5-141
Original file line numberDiff line numberDiff line change
@@ -36,149 +36,13 @@ We tried to make the right thing easy, but if you ignore these guidelines you ma
3636
untestable application.
3737

3838
## Dependency Injection
39-
There are several ways in which you can get a hold of a dependency. You can:
40-
1. Create it using the `new` operator.
41-
2. Look for it in a well-known place, also known as a global singleton.
42-
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of
43-
the registry? Most likely by looking it up in a well known place. See #2.)
44-
4. Expect it to be handed to you.
4539

46-
Out of the four options in the list above, only the last one is testable. Let's look at why:
40+
Angular comes with {@link di dependency injection} built-in, which makes testing components much
41+
easier, because you can pass in a component's dependencies and stub or mock them as you wish.
4742

48-
### Using the `new` operator
49-
50-
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
51-
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
52-
instantiate an `XHR` that will retrieve data from the server.
53-
54-
```js
55-
function MyClass() {
56-
this.doWork = function() {
57-
var xhr = new XHR();
58-
xhr.open(method, url, true);
59-
xhr.onreadystatechange = function() {...}
60-
xhr.send();
61-
}
62-
}
63-
```
64-
65-
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
66-
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
67-
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
68-
patch, but that is a bad idea for many reasons which are outside the scope of this document.
69-
70-
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
71-
72-
```js
73-
var oldXHR = XHR;
74-
XHR = function MockXHR() {};
75-
var myClass = new MyClass();
76-
myClass.doWork();
77-
// assert that MockXHR got called with the right arguments
78-
XHR = oldXHR; // if you forget this bad things will happen
79-
```
80-
81-
82-
### Global look-up:
83-
Another way to approach the problem is to look for the service in a well-known location.
84-
85-
```js
86-
function MyClass() {
87-
this.doWork = function() {
88-
global.xhr({
89-
method:'...',
90-
url:'...',
91-
complete:function(response){ ... }
92-
})
93-
}
94-
}
95-
```
96-
97-
While no new dependency instance is created, it is fundamentally the same as `new` in
98-
that no way exists to intercept the call to `global.xhr` for testing purposes, other than
99-
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
100-
order to replace it with call to a mock method. For further explanation of why this is bad see: [Brittle Global
101-
State & Singletons](http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/)
102-
103-
The class above is hard to test since we have to change the global state:
104-
105-
```js
106-
var oldXHR = global.xhr;
107-
global.xhr = function mockXHR() {};
108-
var myClass = new MyClass();
109-
myClass.doWork();
110-
// assert that mockXHR got called with the right arguments
111-
global.xhr = oldXHR; // if you forget this bad things will happen
112-
```
113-
114-
115-
### Service Registry:
116-
117-
It may seem that this can be solved by having a registry of all the services and then
118-
having the tests replace the services as needed.
119-
120-
```js
121-
function MyClass() {
122-
var serviceRegistry = ????;
123-
this.doWork = function() {
124-
var xhr = serviceRegistry.get('xhr');
125-
xhr({
126-
method:'...',
127-
url:'...',
128-
complete:function(response){ ... }
129-
})
130-
}
131-
```
132-
133-
However, where does the serviceRegistry come from? If it is:
134-
* `new`-ed up, the test has no chance to reset the services for testing.
135-
* a global look-up then the service returned is global as well (but resetting is easier, since
136-
only one global variable exists to be reset).
137-
138-
The class above is hard to test since we have to change the global state:
139-
140-
```js
141-
var oldServiceLocator = global.serviceLocator;
142-
global.serviceLocator.set('xhr', function mockXHR() {});
143-
var myClass = new MyClass();
144-
myClass.doWork();
145-
// assert that mockXHR got called with the right arguments
146-
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
147-
```
148-
149-
150-
### Passing in Dependencies:
151-
Last, the dependency can be passed in.
152-
153-
```js
154-
function MyClass(xhr) {
155-
this.doWork = function() {
156-
xhr({
157-
method:'...',
158-
url:'...',
159-
complete:function(response){ ... }
160-
})
161-
}
162-
```
163-
164-
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
165-
instead about whoever created the class responsible for passing it in. Since the creator of the
166-
class should be different code than the user of the class, it separates the responsibility of
167-
creation from the logic. This is dependency-injection in a nutshell.
168-
169-
The class above is testable, since in the test we can write:
170-
171-
```js
172-
function xhrMock(args) {...}
173-
var myClass = new MyClass(xhrMock);
174-
myClass.doWork();
175-
// assert that xhrMock got called with the right arguments
176-
```
177-
178-
Notice that no global variables were harmed in the writing of this test.
179-
180-
Angular comes with {@link di dependency injection} built-in, making the right thing
181-
easy to do, but you still need to do it if you wish to take advantage of the testability story.
43+
Components that have their dependencies injected allow them to be easily mocked on a test by
44+
test basis, without having to mess with any global variables that could inadvertently affect
45+
another test.
18246

18347
## Additional tools for testing Angular applications
18448

0 commit comments

Comments
 (0)