Skip to content

Commit 33b5ba8

Browse files
committed
feat(tests): add a test injector
fixes angular#614 Asynchronous test should inject an AsyncTestCompleter: Before: it("async test", (done) => { // ... done(); }); After: it("async test", inject([AsyncTestCompleter], (async) => { // ... async.done(); })); Note: inject() is currently a function and the first parameter is the array of DI tokens to inject as the test function parameters. This construct is linked to Traceur limitations. The planned syntax is: it("async test", @Inject (async: AsyncTestCompleter) => { // ... async.done(); });
1 parent 5926d2e commit 33b5ba8

29 files changed

+1239
-638
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {bind} from 'angular2/di';
2+
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
3+
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
4+
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection} from 'angular2/change_detection';
5+
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
6+
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
7+
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
8+
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
9+
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
10+
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
11+
import {XHRMock} from 'angular2/src/mock/xhr_mock';
12+
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
13+
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
14+
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
15+
import {StyleInliner} from 'angular2/src/core/compiler/style_inliner';
16+
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
17+
18+
import {Injector} from 'angular2/di';
19+
20+
import {List, ListWrapper} from 'angular2/src/facade/collection';
21+
import {FunctionWrapper} from 'angular2/src/facade/lang';
22+
23+
/**
24+
* Returns the root injector bindings.
25+
*
26+
* This must be kept in sync with the _rootBindings in application.js
27+
*
28+
* @returns {*[]}
29+
*/
30+
function _getRootBindings() {
31+
return [
32+
bind(Reflector).toValue(reflector),
33+
];
34+
}
35+
36+
/**
37+
* Returns the application injector bindings.
38+
*
39+
* This must be kept in sync with _injectorBindings() in application.js
40+
*
41+
* @returns {*[]}
42+
*/
43+
function _getAppBindings() {
44+
return [
45+
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
46+
Compiler,
47+
CompilerCache,
48+
TemplateResolver,
49+
bind(ChangeDetection).toValue(dynamicChangeDetection),
50+
TemplateLoader,
51+
DirectiveMetadataReader,
52+
Parser,
53+
Lexer,
54+
ExceptionHandler,
55+
bind(XHR).toClass(XHRMock),
56+
ComponentUrlMapper,
57+
UrlResolver,
58+
StyleUrlResolver,
59+
StyleInliner,
60+
bind(CssProcessor).toFactory(() => new CssProcessor(null), []),
61+
];
62+
}
63+
64+
export function createTestInjector(bindings: List) {
65+
var rootInjector = new Injector(_getRootBindings());
66+
return rootInjector.createChild(ListWrapper.concat(_getAppBindings(), bindings));
67+
}
68+
69+
/**
70+
* Allows injecting dependencies in beforeEach() and it().
71+
*
72+
* Example:
73+
*
74+
* beforeEach(inject([Dependency, AClass], (dep, object) => {
75+
* // some code that uses `dep` and `object`
76+
* // ...
77+
* }));
78+
*
79+
* it('...', inject([AClass, AsyncTestCompleter], (object, async) => {
80+
* object.doSomething().then(() => {
81+
* expect(...);
82+
* async.done();
83+
* });
84+
* })
85+
*
86+
* Notes:
87+
* - injecting an `AsyncTestCompleter` allow completing async tests - this is the equivalent of
88+
* adding a `done` parameter in Jasmine,
89+
* - inject is currently a function because of some Traceur limitation the syntax should eventually
90+
* becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });`
91+
*
92+
* @param {Array} tokens
93+
* @param {Function} fn
94+
* @return {FunctionWithParamTokens}
95+
*/
96+
export function inject(tokens: List, fn: Function) {
97+
return new FunctionWithParamTokens(tokens, fn);
98+
}
99+
100+
export class FunctionWithParamTokens {
101+
_tokens: List;
102+
_fn: Function;
103+
104+
constructor(tokens: List, fn: Function) {
105+
this._tokens = tokens;
106+
this._fn = fn;
107+
}
108+
109+
execute(injector: Injector) {
110+
var params = ListWrapper.map(this._tokens, (t) => injector.get(t));
111+
FunctionWrapper.apply(this._fn, params);
112+
}
113+
}
114+

modules/angular2/src/test_lib/test_lib.dart

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,76 @@
11
library test_lib.test_lib;
22

33
import 'package:guinness/guinness.dart' as gns;
4-
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit;
4+
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
55
import 'package:unittest/unittest.dart' hide expect;
66
import 'dart:mirrors';
77
import 'dart:async';
8-
import 'package:angular2/src/reflection/reflection.dart';
9-
import 'package:angular2/src/reflection/reflection_capabilities.dart';
8+
109
import 'package:collection/equality.dart';
1110
import 'package:angular2/src/dom/dom_adapter.dart' show DOM;
1211

12+
import 'package:angular2/src/reflection/reflection.dart';
13+
import 'package:angular2/src/reflection/reflection_capabilities.dart';
14+
15+
import 'package:angular2/src/di/binding.dart' show bind;
16+
import 'package:angular2/src/di/injector.dart' show Injector;
17+
18+
import './test_injector.dart';
19+
export './test_injector.dart' show inject;
20+
1321
bool IS_DARTIUM = true;
1422
bool IS_NODEJS = false;
1523

24+
List _testBindings = [];
25+
Injector _injector;
26+
bool _isCurrentTestAsync;
27+
bool _inIt = false;
28+
29+
class AsyncTestCompleter {
30+
Completer _completer;
31+
32+
AsyncTestCompleter() {
33+
_completer = new Completer();
34+
}
35+
36+
done() {
37+
_completer.complete();
38+
}
39+
40+
get future => _completer.future;
41+
}
42+
43+
testSetup() {
44+
reflector.reflectionCapabilities = new ReflectionCapabilities();
45+
// beforeEach configuration:
46+
// - Priority 3: clear the bindings before each test,
47+
// - Priority 2: collect the bindings before each test, see beforeEachBindings(),
48+
// - Priority 1: create the test injector to be used in beforeEach() and it()
49+
50+
gns.beforeEach(
51+
() {
52+
_testBindings.clear();
53+
},
54+
priority: 3
55+
);
56+
57+
var completerBinding = bind(AsyncTestCompleter).toFactory(() {
58+
// Mark the test as async when an AsyncTestCompleter is injected in an it(),
59+
if (!_inIt) throw 'AsyncTestCompleter can only be injected in an "it()"';
60+
_isCurrentTestAsync = true;
61+
return new AsyncTestCompleter();
62+
});
63+
64+
gns.beforeEach(
65+
() {
66+
_isCurrentTestAsync = false;
67+
_testBindings.add(completerBinding);
68+
_injector = createTestInjector(_testBindings);
69+
},
70+
priority: 1
71+
);
72+
}
73+
1674
Expect expect(actual, [matcher]) {
1775
final expect = new Expect(actual);
1876
if (matcher != null) expect.to(matcher);
@@ -46,38 +104,55 @@ class NotExpect extends gns.NotExpect {
46104
}
47105

48106
beforeEach(fn) {
49-
gns.beforeEach(_enableReflection(fn));
107+
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
108+
gns.beforeEach(() {
109+
fn.execute(_injector);
110+
});
50111
}
51112

52-
it(name, fn) {
53-
gns.it(name, _enableReflection(_handleAsync(fn)));
113+
/**
114+
* Allows overriding default bindings defined in test_injector.js.
115+
*
116+
* The given function must return a list of DI bindings.
117+
*
118+
* Example:
119+
*
120+
* beforeEachBindings(() => [
121+
* bind(Compiler).toClass(MockCompiler),
122+
* bind(SomeToken).toValue(myValue),
123+
* ]);
124+
*/
125+
beforeEachBindings(fn) {
126+
gns.beforeEach(
127+
() {
128+
var bindings = fn();
129+
if (bindings != null) _testBindings.addAll(bindings);
130+
},
131+
priority: 2
132+
);
54133
}
55134

56-
iit(name, fn) {
57-
gns.iit(name, _enableReflection(_handleAsync(fn)));
135+
_it(gnsFn, name, fn) {
136+
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
137+
gnsFn(name, () {
138+
_inIt = true;
139+
fn.execute(_injector);
140+
_inIt = false;
141+
if (_isCurrentTestAsync) return _injector.get(AsyncTestCompleter).future;
142+
});
58143
}
59144

60-
_enableReflection(fn) {
61-
return () {
62-
reflector.reflectionCapabilities = new ReflectionCapabilities();
63-
return fn();
64-
};
65-
}
66145

67-
_handleAsync(fn) {
68-
ClosureMirror cm = reflect(fn);
69-
MethodMirror mm = cm.function;
70-
71-
var completer = new Completer();
146+
it(name, fn) {
147+
_it(gns.it, name, fn);
148+
}
72149

73-
if (mm.parameters.length == 1) {
74-
return () {
75-
cm.apply([completer.complete]);
76-
return completer.future;
77-
};
78-
}
150+
iit(name, fn) {
151+
_it(gns.iit, name, fn);
152+
}
79153

80-
return fn;
154+
xit(name, fn) {
155+
_it(gns.xit, name, fn);
81156
}
82157

83158
// TODO(tbosch): remove when https://github.com/vsavkin/guinness/issues/41

0 commit comments

Comments
 (0)