OLD | NEW |
| (Empty) |
1 @Injectables(const [ | |
2 ClassOne, | |
3 CircularA, | |
4 CircularB, | |
5 MultipleConstructors, | |
6 NumDependency, | |
7 IntDependency, | |
8 DoubleDependency, | |
9 BoolDependency, | |
10 StringDependency | |
11 ]) | |
12 library di.tests; | |
13 | |
14 import 'fixed-unittest.dart'; | |
15 import 'package:di/di.dart'; | |
16 import 'package:di/dynamic_injector.dart'; | |
17 import 'package:di/static_injector.dart'; | |
18 import 'package:di/annotations.dart'; | |
19 | |
20 // Generated file. Run ../test_tf_gen.sh. | |
21 import 'type_factories_gen.dart' as type_factories_gen; | |
22 | |
23 /** | |
24 * Annotation used to mark classes for which static type factory must be | |
25 * generated. For testing purposes not all classes are marked with this | |
26 * annotation, some classes are included in @Injectables at the top. | |
27 */ | |
28 class Injectable { | |
29 const Injectable(); | |
30 } | |
31 | |
32 // just some classes for testing | |
33 @Injectable() | |
34 class Engine { | |
35 final String id = 'v8-id'; | |
36 } | |
37 | |
38 @Injectable() | |
39 class MockEngine implements Engine { | |
40 final String id = 'mock-id'; | |
41 } | |
42 | |
43 @Injectable() | |
44 class MockEngine2 implements Engine { | |
45 String id = 'mock-id-2'; | |
46 } | |
47 | |
48 class HiddenConstructor { | |
49 HiddenConstructor._(); | |
50 } | |
51 | |
52 @Injectable() | |
53 class Car { | |
54 Engine engine; | |
55 Injector injector; | |
56 | |
57 Car(this.engine, this.injector); | |
58 } | |
59 | |
60 class Lemon { | |
61 final engine; | |
62 final Injector injector; | |
63 | |
64 Lemon(this.engine, this.injector); | |
65 } | |
66 | |
67 class NumDependency { | |
68 NumDependency(num value) {} | |
69 } | |
70 | |
71 class IntDependency { | |
72 IntDependency(int value) {} | |
73 } | |
74 | |
75 class DoubleDependency { | |
76 DoubleDependency(double value) {} | |
77 } | |
78 | |
79 class StringDependency { | |
80 StringDependency(String value) {} | |
81 } | |
82 | |
83 class BoolDependency { | |
84 BoolDependency(bool value) {} | |
85 } | |
86 | |
87 | |
88 class CircularA { | |
89 CircularA(CircularB b) {} | |
90 } | |
91 | |
92 class CircularB { | |
93 CircularB(CircularA a) {} | |
94 } | |
95 | |
96 typedef int CompareInt(int a, int b); | |
97 | |
98 int compareIntAsc(int a, int b) => b.compareTo(a); | |
99 | |
100 class WithTypeDefDependency { | |
101 CompareInt compare; | |
102 | |
103 WithTypeDefDependency(this.compare); | |
104 } | |
105 | |
106 class MultipleConstructors { | |
107 String instantiatedVia; | |
108 MultipleConstructors() : instantiatedVia = 'default'; | |
109 MultipleConstructors.named() : instantiatedVia = 'named'; | |
110 } | |
111 | |
112 class InterfaceOne { | |
113 } | |
114 | |
115 class ClassOne implements InterfaceOne { | |
116 ClassOne(Log log) { | |
117 log.add('ClassOne'); | |
118 } | |
119 } | |
120 | |
121 @Injectable() | |
122 class ParameterizedType<T1, T2> { | |
123 ParameterizedType(); | |
124 } | |
125 | |
126 @Injectable() | |
127 class ParameterizedDependency { | |
128 final ParameterizedType<bool, int> _p; | |
129 ParameterizedDependency(this._p); | |
130 } | |
131 | |
132 @Injectable() | |
133 class GenericParameterizedDependency { | |
134 final ParameterizedType _p; | |
135 GenericParameterizedDependency(this._p); | |
136 } | |
137 | |
138 @Injectable() | |
139 class Log { | |
140 var log = []; | |
141 | |
142 add(String message) => log.add(message); | |
143 } | |
144 | |
145 class EmulatedMockEngineFactory { | |
146 call(Injector i) => new MockEngine(); | |
147 } | |
148 | |
149 void main() { | |
150 createInjectorSpec('DynamicInjector', | |
151 (modules, [name]) => new DynamicInjector(modules: modules, name: name)); | |
152 | |
153 createInjectorSpec('StaticInjector', | |
154 (modules, [name]) => new StaticInjector(modules: modules, name: name, | |
155 typeFactories: type_factories_gen.typeFactories)); | |
156 | |
157 dynamicInjectorTest(); | |
158 staticInjectorTest(); | |
159 } | |
160 | |
161 typedef Injector InjectorFactory(List<Module> modules, [String name]); | |
162 | |
163 createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { | |
164 | |
165 describe(injectorName, () { | |
166 | |
167 it('should instantiate a type', () { | |
168 var injector = injectorFactory([new Module()..type(Engine)]); | |
169 var instance = injector.get(Engine); | |
170 | |
171 expect(instance, instanceOf(Engine)); | |
172 expect(instance.id, toEqual('v8-id')); | |
173 }); | |
174 | |
175 it('should fail if no binding is found', () { | |
176 var injector = injectorFactory([]); | |
177 expect(() { | |
178 injector.get(Engine); | |
179 }, toThrow(NoProviderError, 'No provider found for Engine! ' | |
180 '(resolving Engine)')); | |
181 }); | |
182 | |
183 | |
184 it('should resolve basic dependencies', () { | |
185 var injector = injectorFactory([new Module()..type(Car)..type(Engine)]); | |
186 var instance = injector.get(Car); | |
187 | |
188 expect(instance, instanceOf(Car)); | |
189 expect(instance.engine.id, toEqual('v8-id')); | |
190 }); | |
191 | |
192 | |
193 it('should inject generic parameterized types', () { | |
194 var injector = injectorFactory([new Module() | |
195 ..type(ParameterizedType) | |
196 ..type(GenericParameterizedDependency) | |
197 ]); | |
198 expect(injector.get(GenericParameterizedDependency), | |
199 new isInstanceOf<GenericParameterizedDependency>()); | |
200 }); | |
201 | |
202 | |
203 xit('should error while resolving parameterized types', () { | |
204 var injector = injectorFactory([new Module() | |
205 ..type(ParameterizedType) | |
206 ..type(ParameterizedDependency) | |
207 ]); | |
208 expect(() => injector.get(ParameterizedDependency), throws); | |
209 }); | |
210 | |
211 | |
212 it('should allow modules and overriding providers', () { | |
213 var module = new Module()..type(Engine, implementedBy: MockEngine); | |
214 | |
215 // injector is immutable | |
216 // you can't load more modules once it's instantiated | |
217 // (you can create a child injector) | |
218 var injector = injectorFactory([module]); | |
219 var instance = injector.get(Engine); | |
220 | |
221 expect(instance.id, toEqual('mock-id')); | |
222 }); | |
223 | |
224 | |
225 it('should only create a single instance', () { | |
226 var injector = injectorFactory([new Module()..type(Engine)]); | |
227 var first = injector.get(Engine); | |
228 var second = injector.get(Engine); | |
229 | |
230 expect(first, toBe(second)); | |
231 }); | |
232 | |
233 | |
234 it('should allow providing values', () { | |
235 var module = new Module() | |
236 ..value(Engine, 'str value') | |
237 ..value(Car, 123); | |
238 | |
239 var injector = injectorFactory([module]); | |
240 var abcInstance = injector.get(Engine); | |
241 var complexInstance = injector.get(Car); | |
242 | |
243 expect(abcInstance, toEqual('str value')); | |
244 expect(complexInstance, toEqual(123)); | |
245 }); | |
246 | |
247 | |
248 it('should allow providing factory functions', () { | |
249 var module = new Module()..factory(Engine, (Injector injector) { | |
250 return 'factory-product'; | |
251 }); | |
252 | |
253 var injector = injectorFactory([module]); | |
254 var instance = injector.get(Engine); | |
255 | |
256 expect(instance, toEqual('factory-product')); | |
257 }); | |
258 | |
259 | |
260 it('should allow providing with emulated factory functions', () { | |
261 var module = new Module(); | |
262 module.factory(Engine, new EmulatedMockEngineFactory()); | |
263 | |
264 var injector = injectorFactory([module]); | |
265 var instance = injector.get(Engine); | |
266 | |
267 expect(instance, new isInstanceOf<MockEngine>()); | |
268 }); | |
269 | |
270 | |
271 it('should inject injector into factory function', () { | |
272 var module = new Module() | |
273 ..type(Engine) | |
274 ..factory(Car, (Injector injector) { | |
275 return new Car(injector.get(Engine), injector); | |
276 }); | |
277 | |
278 var injector = injectorFactory([module]); | |
279 var instance = injector.get(Car); | |
280 | |
281 expect(instance, instanceOf(Car)); | |
282 expect(instance.engine.id, toEqual('v8-id')); | |
283 }); | |
284 | |
285 | |
286 it('should throw an exception when injecting a primitive type', () { | |
287 var injector = injectorFactory([ | |
288 new Module() | |
289 ..type(NumDependency) | |
290 ..type(IntDependency) | |
291 ..type(DoubleDependency) | |
292 ..type(BoolDependency) | |
293 ..type(StringDependency) | |
294 ]); | |
295 | |
296 expect(() { | |
297 injector.get(NumDependency); | |
298 }, toThrow(NoProviderError, 'Cannot inject a primitive type of num! ' | |
299 '(resolving NumDependency -> num)')); | |
300 | |
301 expect(() { | |
302 injector.get(IntDependency); | |
303 }, toThrow(NoProviderError, 'Cannot inject a primitive type of int! ' | |
304 '(resolving IntDependency -> int)')); | |
305 | |
306 expect(() { | |
307 injector.get(DoubleDependency); | |
308 }, toThrow(NoProviderError, 'Cannot inject a primitive type of double! ' | |
309 '(resolving DoubleDependency -> double)')); | |
310 | |
311 expect(() { | |
312 injector.get(BoolDependency); | |
313 }, toThrow(NoProviderError, 'Cannot inject a primitive type of bool! ' | |
314 '(resolving BoolDependency -> bool)')); | |
315 | |
316 expect(() { | |
317 injector.get(StringDependency); | |
318 }, toThrow(NoProviderError, 'Cannot inject a primitive type of String! ' | |
319 '(resolving StringDependency -> String)')); | |
320 }); | |
321 | |
322 | |
323 it('should throw an exception when circular dependency', () { | |
324 var injector = injectorFactory([new Module()..type(CircularA)..type(Circul
arB)]); | |
325 | |
326 expect(() { | |
327 injector.get(CircularA); | |
328 }, toThrow(CircularDependencyError, 'Cannot resolve a circular dependency!
' | |
329 '(resolving CircularA -> ' | |
330 'CircularB -> CircularA)')); | |
331 }); | |
332 | |
333 | |
334 it('should provide the injector as Injector', () { | |
335 var injector = injectorFactory([]); | |
336 | |
337 expect(injector.get(Injector), toBe(injector)); | |
338 }); | |
339 | |
340 | |
341 it('should inject a typedef', () { | |
342 var module = new Module()..value(CompareInt, compareIntAsc); | |
343 | |
344 var injector = injectorFactory([module]); | |
345 var compare = injector.get(CompareInt); | |
346 | |
347 expect(compare(1, 2), toBe(1)); | |
348 expect(compare(5, 2), toBe(-1)); | |
349 }); | |
350 | |
351 | |
352 it('should throw an exception when injecting typedef without providing it',
() { | |
353 var injector = injectorFactory([new Module()..type(WithTypeDefDependency)]
); | |
354 | |
355 expect(() { | |
356 injector.get(WithTypeDefDependency); | |
357 }, throws); | |
358 }); | |
359 | |
360 | |
361 it('should instantiate via the default/unnamed constructor', () { | |
362 var injector = injectorFactory([new Module()..type(MultipleConstructors)])
; | |
363 MultipleConstructors instance = injector.get(MultipleConstructors); | |
364 expect(instance.instantiatedVia, 'default'); | |
365 }); | |
366 | |
367 // CHILD INJECTORS | |
368 it('should inject from child', () { | |
369 var module = new Module()..type(Engine, implementedBy: MockEngine); | |
370 | |
371 var parent = injectorFactory([new Module()..type(Engine)]); | |
372 var child = parent.createChild([module]); | |
373 | |
374 var abcFromParent = parent.get(Engine); | |
375 var abcFromChild = child.get(Engine); | |
376 | |
377 expect(abcFromParent.id, toEqual('v8-id')); | |
378 expect(abcFromChild.id, toEqual('mock-id')); | |
379 }); | |
380 | |
381 | |
382 it('should enumerate across children', () { | |
383 var parent = injectorFactory([new Module()..type(Engine)]); | |
384 var child = parent.createChild([new Module()..type(MockEngine)]); | |
385 | |
386 expect(parent.types, unorderedEquals(new Set.from([Engine, Injector]))); | |
387 expect(child.types, unorderedEquals(new Set.from([Engine, MockEngine, Inje
ctor]))); | |
388 }); | |
389 | |
390 | |
391 it('should inject instance from parent if not provided in child', () { | |
392 var module = new Module()..type(Car); | |
393 | |
394 var parent = injectorFactory([new Module()..type(Car)..type(Engine)]); | |
395 var child = parent.createChild([module]); | |
396 | |
397 var complexFromParent = parent.get(Car); | |
398 var complexFromChild = child.get(Car); | |
399 var abcFromParent = parent.get(Engine); | |
400 var abcFromChild = child.get(Engine); | |
401 | |
402 expect(complexFromChild, not(toBe(complexFromParent))); | |
403 expect(abcFromChild, toBe(abcFromParent)); | |
404 }); | |
405 | |
406 | |
407 it('should inject instance from parent but never use dependency from child',
() { | |
408 var module = new Module()..type(Engine, implementedBy: MockEngine); | |
409 | |
410 var parent = injectorFactory([new Module()..type(Car)..type(Engine)]); | |
411 var child = parent.createChild([module]); | |
412 | |
413 var complexFromParent = parent.get(Car); | |
414 var complexFromChild = child.get(Car); | |
415 var abcFromParent = parent.get(Engine); | |
416 var abcFromChild = child.get(Engine); | |
417 | |
418 expect(complexFromChild, toBe(complexFromParent)); | |
419 expect(complexFromChild.engine, toBe(abcFromParent)); | |
420 expect(complexFromChild.engine, not(toBe(abcFromChild))); | |
421 }); | |
422 | |
423 | |
424 it('should force new instance in child even if already instantiated in paren
t', () { | |
425 var parent = injectorFactory([new Module()..type(Engine)]); | |
426 var abcAlreadyInParent = parent.get(Engine); | |
427 | |
428 var child = parent.createChild([], forceNewInstances: [Engine]); | |
429 var abcFromChild = child.get(Engine); | |
430 | |
431 expect(abcFromChild, not(toBe(abcAlreadyInParent))); | |
432 }); | |
433 | |
434 | |
435 it('should force new instance in child using provider from grand parent', ()
{ | |
436 var module = new Module()..type(Engine, implementedBy: MockEngine); | |
437 | |
438 var grandParent = injectorFactory([module]); | |
439 var parent = grandParent.createChild([]); | |
440 var child = parent.createChild([], forceNewInstances: [Engine]); | |
441 | |
442 var abcFromGrandParent = grandParent.get(Engine); | |
443 var abcFromChild = child.get(Engine); | |
444 | |
445 expect(abcFromChild.id, toEqual(('mock-id'))); | |
446 expect(abcFromChild, not(toBe(abcFromGrandParent))); | |
447 }); | |
448 | |
449 | |
450 it('should provide child injector as Injector', () { | |
451 var injector = injectorFactory([]); | |
452 var child = injector.createChild([]); | |
453 | |
454 expect(child.get(Injector), toBe(child)); | |
455 }); | |
456 | |
457 | |
458 it('should set the injector name', () { | |
459 var injector = injectorFactory([], 'foo'); | |
460 expect(injector.name, 'foo'); | |
461 }); | |
462 | |
463 | |
464 it('should set the child injector name', () { | |
465 var injector = injectorFactory([], 'foo'); | |
466 var childInjector = injector.createChild(null, name: 'bar'); | |
467 expect(childInjector.name, 'bar'); | |
468 }); | |
469 | |
470 | |
471 it('should instantiate class only once (Issue #18)', () { | |
472 var injector = injectorFactory([ | |
473 new Module() | |
474 ..type(Log) | |
475 ..type(ClassOne) | |
476 ..factory(InterfaceOne, (i) => i.get(ClassOne)) | |
477 ]); | |
478 | |
479 expect(injector.get(InterfaceOne), same(injector.get(ClassOne))); | |
480 expect(injector.get(Log).log.join(' '), 'ClassOne'); | |
481 }); | |
482 | |
483 | |
484 describe('creation strategy', () { | |
485 | |
486 it('should get called for instance creation', () { | |
487 | |
488 List creationLog = []; | |
489 dynamic creation(Injector requesting, Injector defining, factory) { | |
490 creationLog.add([requesting, defining]); | |
491 return factory(); | |
492 } | |
493 | |
494 var parentModule = new Module() | |
495 ..type(Engine, implementedBy: MockEngine, creation: creation) | |
496 ..type(Car, creation: creation); | |
497 | |
498 var parentInjector = injectorFactory([parentModule]); | |
499 var childInjector = parentInjector.createChild([]); | |
500 childInjector.get(Car); | |
501 expect(creationLog, [ | |
502 [childInjector, parentInjector], | |
503 [childInjector, parentInjector] | |
504 ]); | |
505 }); | |
506 | |
507 it('should be able to prevent instantiation', () { | |
508 | |
509 List creationLog = []; | |
510 dynamic creation(Injector requesting, Injector defining, factory) { | |
511 throw 'not allowing'; | |
512 } | |
513 | |
514 var module = new Module() | |
515 ..type(Engine, implementedBy: MockEngine, creation: creation); | |
516 var injector = injectorFactory([module]); | |
517 expect(() { | |
518 injector.get(Engine); | |
519 }, throwsA('not allowing')); | |
520 }); | |
521 }); | |
522 | |
523 | |
524 describe('visiblity', () { | |
525 | |
526 it('should hide instances', () { | |
527 | |
528 var rootMock = new MockEngine(); | |
529 var childMock = new MockEngine(); | |
530 | |
531 var parentModule = new Module() | |
532 ..value(Engine, rootMock); | |
533 var childModule = new Module() | |
534 ..value(Engine, childMock, visibility: (_, __) => false); | |
535 | |
536 var parentInjector = injectorFactory([parentModule]); | |
537 var childInjector = parentInjector.createChild([childModule]); | |
538 | |
539 var val = childInjector.get(Engine); | |
540 expect(val, same(rootMock)); | |
541 }); | |
542 | |
543 it('should throw when an instance in not visible in the root injector', ()
{ | |
544 var module = new Module() | |
545 ..value(Car, 'Invisible', visibility: (_, __) => false); | |
546 | |
547 var injector = injectorFactory([module]); | |
548 | |
549 expect(() { | |
550 injector.get(Car); | |
551 }, toThrow( | |
552 NoProviderError, | |
553 'No provider found for Car! (resolving Car)' | |
554 )); | |
555 }); | |
556 | |
557 }); | |
558 | |
559 }); | |
560 | |
561 } | |
562 | |
563 void dynamicInjectorTest() { | |
564 describe('DynamicInjector', () { | |
565 | |
566 it('should throw a comprehensible error message on untyped argument', () { | |
567 var module = new Module()..type(Lemon)..type(Engine); | |
568 var injector = new DynamicInjector(modules : [module]); | |
569 | |
570 expect(() { | |
571 injector.get(Lemon); | |
572 }, toThrow(NoProviderError, "The 'engine' parameter must be typed " | |
573 "(resolving Lemon)")); | |
574 }); | |
575 | |
576 it('should throw a comprehensible error message when no default constructor
found', () { | |
577 var module = new Module()..type(HiddenConstructor); | |
578 var injector = new DynamicInjector(modules: [module]); | |
579 | |
580 expect(() { | |
581 injector.get(HiddenConstructor); | |
582 }, toThrow(NoProviderError, startsWith('Unable to find default ' | |
583 'constructor for HiddenConstructor. Make sure class has a ' | |
584 'default constructor.'))); | |
585 }); | |
586 | |
587 }); | |
588 } | |
589 | |
590 void staticInjectorTest() { | |
591 describe('StaticInjector', () { | |
592 | |
593 it('should use type factories passed in the constructor', () { | |
594 var module = new Module() | |
595 ..type(Engine); | |
596 var injector = new StaticInjector(modules: [module], typeFactories: { | |
597 Engine: (f) => new Engine() | |
598 }); | |
599 | |
600 var engine; | |
601 expect(() { | |
602 engine = injector.get(Engine); | |
603 }, isNot(throws)); | |
604 expect(engine, new isInstanceOf<Engine>()); | |
605 }); | |
606 | |
607 it('should use type factories passes in one module', () { | |
608 var module = new Module() | |
609 ..type(Engine) | |
610 ..typeFactories = { | |
611 Engine: (f) => new Engine() | |
612 }; | |
613 var injector = new StaticInjector(modules: [module]); | |
614 | |
615 var engine; | |
616 expect(() { | |
617 engine = injector.get(Engine); | |
618 }, isNot(throws)); | |
619 expect(engine, new isInstanceOf<Engine>()); | |
620 }); | |
621 | |
622 it('should use type factories passes in many modules', () { | |
623 var module1 = new Module() | |
624 ..type(Engine) | |
625 ..typeFactories = { | |
626 Engine: (f) => new Engine() | |
627 }; | |
628 var module2 = new Module() | |
629 ..type(Car) | |
630 ..typeFactories = { | |
631 Car: (f) => new Car(f(Engine), f(Injector)) | |
632 }; | |
633 | |
634 var injector = new StaticInjector(modules: [module1, module2]); | |
635 | |
636 var engine; | |
637 expect(() { | |
638 engine = injector.get(Car); | |
639 }, isNot(throws)); | |
640 expect(engine, new isInstanceOf<Car>()); | |
641 }); | |
642 | |
643 it('should use type factories passes in hierarchical module', () { | |
644 var module = new Module() | |
645 ..type(Engine) | |
646 ..typeFactories = { | |
647 Engine: (f) => new Engine() | |
648 }; | |
649 | |
650 module.install(new Module() | |
651 ..type(Car) | |
652 ..typeFactories = { | |
653 Car: (f) => new Car(f(Engine), f(Injector)) | |
654 }); | |
655 | |
656 var injector = new StaticInjector(modules: [module]); | |
657 | |
658 var engine; | |
659 expect(() { | |
660 engine = injector.get(Car); | |
661 }, isNot(throws)); | |
662 expect(engine, new isInstanceOf<Car>()); | |
663 }); | |
664 | |
665 it('should find type factories from parent injector', () { | |
666 var module1 = new Module() | |
667 ..type(Engine) | |
668 ..typeFactories = { | |
669 Engine: (f) => new Engine() | |
670 }; | |
671 var module2 = new Module() | |
672 ..type(Car) | |
673 ..typeFactories = { | |
674 Car: (f) => new Car(f(Engine), f(Injector)) | |
675 }; | |
676 | |
677 var rootInjector = new StaticInjector(modules: [module1]); | |
678 var childInjector = rootInjector.createChild([module2]); | |
679 | |
680 expect(() { | |
681 rootInjector.get(Car); | |
682 }, throws); | |
683 | |
684 var engine; | |
685 expect(() { | |
686 engine = childInjector.get(Car); | |
687 }, isNot(throws)); | |
688 expect(engine, new isInstanceOf<Car>()); | |
689 }); | |
690 | |
691 }); | |
692 } | |
OLD | NEW |