1 /** 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject; 18 19 import static com.google.inject.Asserts.assertContains; 20 import static com.google.inject.name.Names.named; 21 import static java.lang.annotation.ElementType.METHOD; 22 import static java.lang.annotation.ElementType.TYPE; 23 import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.Lists; 27 import com.google.inject.binder.AnnotatedBindingBuilder; 28 import com.google.inject.binder.ScopedBindingBuilder; 29 import com.google.inject.name.Named; 30 import com.google.inject.util.Providers; 31 32 import junit.framework.Test; 33 import junit.framework.TestCase; 34 import junit.framework.TestSuite; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.Target; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.concurrent.atomic.AtomicInteger; 41 42 /** 43 * @author jessewilson (at) google.com (Jesse Wilson) 44 */ 45 public class BinderTestSuite extends TestCase { 46 47 public static Test suite() { 48 TestSuite suite = new TestSuite(); 49 50 new Builder() 51 .name("bind A") 52 .module(new AbstractModule() { 53 protected void configure() { 54 bind(A.class); 55 } 56 }) 57 .creationException("No implementation for %s was bound", A.class.getName()) 58 .addToSuite(suite); 59 60 new Builder() 61 .name("bind PlainA named apple") 62 .module(new AbstractModule() { 63 protected void configure() { 64 bind(PlainA.class).annotatedWith(named("apple")); 65 } 66 }) 67 .creationException("No implementation for %s annotated with %s was bound", 68 PlainA.class.getName(), named("apple")) 69 .addToSuite(suite); 70 71 new Builder() 72 .name("bind A to new PlainA(1)") 73 .module(new AbstractModule() { 74 protected void configure() { 75 bind(A.class).toInstance(new PlainA(1)); 76 } 77 }) 78 .creationTime(CreationTime.NONE) 79 .expectedValues(new PlainA(1), new PlainA(1), new PlainA(1)) 80 .addToSuite(suite); 81 82 new Builder() 83 .name("no binding, AWithProvidedBy") 84 .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class) 85 .addToSuite(suite); 86 87 new Builder() 88 .name("no binding, AWithImplementedBy") 89 .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class) 90 .addToSuite(suite); 91 92 new Builder() 93 .name("no binding, ScopedA") 94 .key(Key.get(ScopedA.class), InjectsScopedA.class) 95 .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)) 96 .addToSuite(suite); 97 98 new Builder() 99 .name("no binding, AWithProvidedBy named apple") 100 .key(Key.get(AWithProvidedBy.class, named("apple")), 101 InjectsAWithProvidedByNamedApple.class) 102 .configurationException("No implementation for %s annotated with %s was bound", 103 AWithProvidedBy.class.getName(), named("apple")) 104 .addToSuite(suite); 105 106 new Builder() 107 .name("no binding, AWithImplementedBy named apple") 108 .key(Key.get(AWithImplementedBy.class, named("apple")), 109 InjectsAWithImplementedByNamedApple.class) 110 .configurationException("No implementation for %s annotated with %s was bound", 111 AWithImplementedBy.class.getName(), named("apple")) 112 .addToSuite(suite); 113 114 new Builder() 115 .name("no binding, ScopedA named apple") 116 .key(Key.get(ScopedA.class, named("apple")), InjectsScopedANamedApple.class) 117 .configurationException("No implementation for %s annotated with %s was bound", 118 ScopedA.class.getName(), named("apple")) 119 .addToSuite(suite); 120 121 for (final Scoper scoper : Scoper.values()) { 122 new Builder() 123 .name("bind PlainA") 124 .key(Key.get(PlainA.class), InjectsPlainA.class) 125 .module(new AbstractModule() { 126 protected void configure() { 127 AnnotatedBindingBuilder<PlainA> abb = bind(PlainA.class); 128 scoper.configure(abb); 129 } 130 }) 131 .scoper(scoper) 132 .addToSuite(suite); 133 134 new Builder() 135 .name("bind A to PlainA") 136 .module(new AbstractModule() { 137 protected void configure() { 138 ScopedBindingBuilder sbb = bind(A.class).to(PlainA.class); 139 scoper.configure(sbb); 140 } 141 }) 142 .scoper(scoper) 143 .addToSuite(suite); 144 145 new Builder() 146 .name("bind A to PlainAProvider.class") 147 .module(new AbstractModule() { 148 protected void configure() { 149 ScopedBindingBuilder sbb = bind(A.class).toProvider(PlainAProvider.class); 150 scoper.configure(sbb); 151 } 152 }) 153 .scoper(scoper) 154 .addToSuite(suite); 155 156 new Builder() 157 .name("bind A to new PlainAProvider()") 158 .module(new AbstractModule() { 159 protected void configure() { 160 ScopedBindingBuilder sbb = bind(A.class).toProvider(new PlainAProvider()); 161 scoper.configure(sbb); 162 } 163 }) 164 .scoper(scoper) 165 .addToSuite(suite); 166 167 new Builder() 168 .name("bind AWithProvidedBy") 169 .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class) 170 .module(new AbstractModule() { 171 protected void configure() { 172 ScopedBindingBuilder sbb = bind(AWithProvidedBy.class); 173 scoper.configure(sbb); 174 } 175 }) 176 .scoper(scoper) 177 .addToSuite(suite); 178 179 new Builder() 180 .name("bind AWithImplementedBy") 181 .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class) 182 .module(new AbstractModule() { 183 protected void configure() { 184 ScopedBindingBuilder sbb = bind(AWithImplementedBy.class); 185 scoper.configure(sbb); 186 } 187 }) 188 .scoper(scoper) 189 .addToSuite(suite); 190 191 new Builder() 192 .name("bind ScopedA") 193 .key(Key.get(ScopedA.class), InjectsScopedA.class) 194 .module(new AbstractModule() { 195 protected void configure() { 196 ScopedBindingBuilder sbb = bind(ScopedA.class); 197 scoper.configure(sbb); 198 } 199 }) 200 .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)) 201 .scoper(scoper) 202 .addToSuite(suite); 203 204 205 new Builder() 206 .name("bind AWithProvidedBy named apple") 207 .module(new AbstractModule() { 208 protected void configure() { 209 scoper.configure(bind(AWithProvidedBy.class).annotatedWith(named("apple"))); 210 } 211 }) 212 .creationException("No implementation for %s annotated with %s was bound", 213 AWithProvidedBy.class.getName(), named("apple")) 214 .addToSuite(suite); 215 216 new Builder() 217 .name("bind AWithImplementedBy named apple") 218 .module(new AbstractModule() { 219 protected void configure() { 220 scoper.configure(bind(AWithImplementedBy.class).annotatedWith(named("apple"))); 221 } 222 }) 223 .creationException("No implementation for %s annotated with %s was bound", 224 AWithImplementedBy.class.getName(), named("apple")) 225 .addToSuite(suite); 226 227 new Builder() 228 .name("bind ScopedA named apple") 229 .module(new AbstractModule() { 230 protected void configure() { 231 scoper.configure(bind(ScopedA.class).annotatedWith(named("apple"))); 232 } 233 }) 234 .creationException("No implementation for %s annotated with %s was bound", 235 ScopedA.class.getName(), named("apple")) 236 .addToSuite(suite); 237 238 } 239 240 return suite; 241 } 242 243 enum Scoper { 244 UNSCOPED { 245 void configure(ScopedBindingBuilder sbb) {} 246 void apply(Builder builder) {} 247 }, 248 249 EAGER_SINGLETON { 250 void configure(ScopedBindingBuilder sbb) { 251 sbb.asEagerSingleton(); 252 } 253 void apply(Builder builder) { 254 builder.expectedValues(new PlainA(101), new PlainA(101), new PlainA(101)); 255 builder.creationTime(CreationTime.EAGER); 256 } 257 }, 258 259 SCOPES_SINGLETON { 260 void configure(ScopedBindingBuilder sbb) { 261 sbb.in(Scopes.SINGLETON); 262 } 263 void apply(Builder builder) { 264 builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201)); 265 } 266 }, 267 268 SINGLETON_DOT_CLASS { 269 void configure(ScopedBindingBuilder sbb) { 270 sbb.in(Singleton.class); 271 } 272 void apply(Builder builder) { 273 builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201)); 274 } 275 }, 276 277 TWO_AT_A_TIME_SCOPED_DOT_CLASS { 278 void configure(ScopedBindingBuilder sbb) { 279 sbb.in(TwoAtATimeScoped.class); 280 } 281 void apply(Builder builder) { 282 builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)); 283 } 284 }, 285 286 TWO_AT_A_TIME_SCOPE { 287 void configure(ScopedBindingBuilder sbb) { 288 sbb.in(new TwoAtATimeScope()); 289 } 290 void apply(Builder builder) { 291 builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202)); 292 } 293 }; 294 295 abstract void configure(ScopedBindingBuilder sbb); 296 abstract void apply(Builder builder); 297 } 298 299 /** When Guice creates a value, directly or via a provider */ 300 enum CreationTime { 301 NONE, EAGER, LAZY 302 } 303 304 public static class Builder { 305 private String name = "test"; 306 private Key<?> key = Key.get(A.class); 307 private Class<? extends Injectable> injectsKey = InjectsA.class; 308 private List<Module> modules = Lists.<Module>newArrayList(new AbstractModule() { 309 protected void configure() { 310 bindScope(TwoAtATimeScoped.class, new TwoAtATimeScope()); 311 } 312 }); 313 private List<Object> expectedValues = Lists.<Object>newArrayList( 314 new PlainA(201), new PlainA(202), new PlainA(203)); 315 private CreationTime creationTime = CreationTime.LAZY; 316 private String creationException; 317 private String configurationException; 318 319 public Builder module(Module module) { 320 this.modules.add(module); 321 return this; 322 } 323 324 public Builder creationTime(CreationTime creationTime) { 325 this.creationTime = creationTime; 326 return this; 327 } 328 329 public Builder name(String name) { 330 this.name = name; 331 return this; 332 } 333 334 public Builder key(Key<?> key, Class<? extends Injectable> injectsKey) { 335 this.key = key; 336 this.injectsKey = injectsKey; 337 return this; 338 } 339 340 private Builder creationException(String message, Object... args) { 341 this.creationException = String.format(message, args); 342 return this; 343 } 344 345 private Builder configurationException(String message, Object... args) { 346 configurationException = String.format(message, args); 347 return this; 348 } 349 350 private Builder scoper(Scoper scoper) { 351 name(name + " in " + scoper); 352 scoper.apply(this); 353 return this; 354 } 355 356 private <T> Builder expectedValues(T... values) { 357 this.expectedValues.clear(); 358 Collections.addAll(this.expectedValues, values); 359 return this; 360 } 361 362 public void addToSuite(TestSuite suite) { 363 if (creationException != null) { 364 suite.addTest(new CreationExceptionTest(this)); 365 366 } else if (configurationException != null) { 367 suite.addTest(new ConfigurationExceptionTest(this)); 368 369 } else { 370 suite.addTest(new SuccessTest(this)); 371 if (creationTime != CreationTime.NONE) { 372 suite.addTest(new UserExceptionsTest(this)); 373 } 374 } 375 } 376 } 377 378 public static class SuccessTest extends TestCase { 379 final String name; 380 final Key<?> key; 381 final Class<? extends Injectable> injectsKey; 382 final ImmutableList<Module> modules; 383 final ImmutableList<Object> expectedValues; 384 385 public SuccessTest(Builder builder) { 386 super("test"); 387 name = builder.name; 388 key = builder.key; 389 injectsKey = builder.injectsKey; 390 modules = ImmutableList.copyOf(builder.modules); 391 expectedValues = ImmutableList.copyOf(builder.expectedValues); 392 } 393 394 public String getName() { 395 return name; 396 } 397 398 Injector newInjector() { 399 nextId.set(101); 400 return Guice.createInjector(modules); 401 } 402 403 public void test() throws IllegalAccessException, InstantiationException { 404 Injector injector = newInjector(); 405 nextId.set(201); 406 for (Object value : expectedValues) { 407 assertEquals(value, injector.getInstance(key)); 408 } 409 410 Provider<?> provider = newInjector().getProvider(key); 411 nextId.set(201); 412 for (Object value : expectedValues) { 413 assertEquals(value, provider.get()); 414 } 415 416 Provider<?> bindingProvider = newInjector().getBinding(key).getProvider(); 417 nextId.set(201); 418 for (Object value : expectedValues) { 419 assertEquals(value, bindingProvider.get()); 420 } 421 422 injector = newInjector(); 423 nextId.set(201); 424 for (Object value : expectedValues) { 425 Injectable instance = injector.getInstance(injectsKey); 426 assertEquals(value, instance.value); 427 } 428 429 injector = newInjector(); 430 nextId.set(201); 431 for (Object value : expectedValues) { 432 Injectable injectable = injectsKey.newInstance(); 433 injector.injectMembers(injectable); 434 assertEquals(value, injectable.value); 435 } 436 437 Injector injector1 = newInjector(); 438 nextId.set(201); 439 Injectable hasProvider = injector1.getInstance(injectsKey); 440 hasProvider.provider.get(); 441 nextId.set(201); 442 for (Object value : expectedValues) { 443 assertEquals(value, hasProvider.provider.get()); 444 } 445 } 446 } 447 448 public static class CreationExceptionTest extends TestCase { 449 final String name; 450 final Key<?> key; 451 final ImmutableList<Module> modules; 452 final String creationException; 453 454 public CreationExceptionTest(Builder builder) { 455 super("test"); 456 name = builder.name; 457 key = builder.key; 458 modules = ImmutableList.copyOf(builder.modules); 459 creationException = builder.creationException; 460 } 461 462 public String getName() { 463 return "creation errors:" + name; 464 } 465 466 public void test() { 467 try { 468 Guice.createInjector(modules); 469 fail(); 470 } catch (CreationException expected) { 471 assertContains(expected.getMessage(), creationException); 472 } 473 } 474 } 475 476 public static class ConfigurationExceptionTest extends TestCase { 477 final String name; 478 final Key<?> key; 479 final Class<? extends Injectable> injectsKey; 480 final ImmutableList<Module> modules; 481 final String configurationException; 482 483 public ConfigurationExceptionTest(Builder builder) { 484 super("test"); 485 name = builder.name; 486 key = builder.key; 487 injectsKey = builder.injectsKey; 488 modules = ImmutableList.copyOf(builder.modules); 489 configurationException = builder.configurationException; 490 } 491 492 public String getName() { 493 return "provision errors:" + name; 494 } 495 496 Injector newInjector() { 497 return Guice.createInjector(modules); 498 } 499 500 public void test() throws IllegalAccessException, InstantiationException { 501 try { 502 newInjector().getProvider(key); 503 fail(); 504 } catch (ConfigurationException expected) { 505 assertContains(expected.getMessage(), configurationException); 506 } 507 508 try { 509 newInjector().getBinding(key).getProvider(); 510 fail(); 511 } catch (ConfigurationException expected) { 512 assertContains(expected.getMessage(), configurationException); 513 } 514 515 try { 516 newInjector().getInstance(key); 517 fail(); 518 } catch (ConfigurationException expected) { 519 assertContains(expected.getMessage(), configurationException); 520 } 521 522 try { 523 newInjector().getInstance(injectsKey); 524 fail(); 525 } catch (ConfigurationException expected) { 526 assertContains(expected.getMessage(), 527 configurationException, injectsKey.getName() + ".inject", 528 configurationException, injectsKey.getName() + ".inject", 529 "2 errors"); 530 } 531 532 try { 533 Injectable injectable = injectsKey.newInstance(); 534 newInjector().injectMembers(injectable); 535 fail(); 536 } catch (ConfigurationException expected) { 537 assertContains(expected.getMessage(), 538 configurationException, injectsKey.getName() + ".inject", 539 configurationException, injectsKey.getName() + ".inject", 540 "2 errors"); 541 } 542 } 543 } 544 545 public static class UserExceptionsTest extends TestCase { 546 final String name; 547 final Key<?> key; 548 final Class<? extends Injectable> injectsKey; 549 final ImmutableList<Module> modules; 550 final ImmutableList<Object> expectedValues; 551 final CreationTime creationTime; 552 553 public UserExceptionsTest(Builder builder) { 554 super("test"); 555 name = builder.name; 556 key = builder.key; 557 injectsKey = builder.injectsKey; 558 modules = ImmutableList.copyOf(builder.modules); 559 expectedValues = ImmutableList.copyOf(builder.expectedValues); 560 creationTime = builder.creationTime; 561 } 562 563 public String getName() { 564 return "provision errors:" + name; 565 } 566 567 Injector newInjector() { 568 return Guice.createInjector(modules); 569 } 570 571 public void test() throws IllegalAccessException, InstantiationException { 572 nextId.set(-1); 573 try { 574 newInjector(); 575 assertEquals(CreationTime.LAZY, creationTime); 576 } catch (CreationException expected) { 577 assertEquals(CreationTime.EAGER, creationTime); 578 assertContains(expected.getMessage(), "Illegal value: -1"); 579 return; 580 } 581 582 Provider<?> provider = newInjector().getProvider(key); 583 Provider<?> bindingProvider = newInjector().getBinding(key).getProvider(); 584 585 nextId.set(-1); 586 try { 587 newInjector().getInstance(key); 588 fail(); 589 } catch (ProvisionException expected) { 590 assertContains(expected.getMessage(), "Illegal value: -1"); 591 } 592 593 nextId.set(-1); 594 try { 595 provider.get(); 596 fail(); 597 } catch (ProvisionException expected) { 598 assertContains(expected.getMessage(), "Illegal value: -1"); 599 } 600 601 nextId.set(-1); 602 try { 603 bindingProvider.get(); 604 fail(); 605 } catch (ProvisionException expected) { 606 assertContains(expected.getMessage(), "Illegal value: -1"); 607 } 608 609 try { 610 nextId.set(-1); 611 newInjector().getInstance(injectsKey); 612 } catch (ProvisionException expected) { 613 assertContains(expected.getMessage(), "Illegal value: -1", 614 "for parameter 0 at " + injectsKey.getName() + ".inject"); 615 } 616 617 nextId.set(201); 618 Injectable injectable = injectsKey.newInstance(); 619 try { 620 nextId.set(-1); 621 newInjector().injectMembers(injectable); 622 } catch (ProvisionException expected) { 623 assertContains(expected.getMessage(), "Illegal value: -1", 624 "for parameter 0 at " + injectsKey.getName() + ".inject"); 625 } 626 627 nextId.set(201); 628 Injectable hasProvider = newInjector().getInstance(injectsKey); 629 hasProvider.provider.get(); 630 try { 631 nextId.set(-1); 632 hasProvider.provider.get(); 633 } catch (ProvisionException expected) { 634 assertContains(expected.getMessage(), "Illegal value: -1"); 635 } 636 } 637 } 638 639 /** negative to throw, 101... for eager singletons, 201... for everything else */ 640 static final AtomicInteger nextId = new AtomicInteger(); 641 642 @ProvidedBy(PlainAProvider.class) 643 interface AWithProvidedBy {} 644 645 static class InjectsAWithProvidedBy extends Injectable { 646 @Inject public void inject(AWithProvidedBy aWithProvidedBy, 647 Provider<AWithProvidedBy> aWithProvidedByProvider) { 648 this.value = aWithProvidedBy; 649 this.provider = aWithProvidedByProvider; 650 } 651 } 652 653 static class InjectsAWithProvidedByNamedApple extends Injectable { 654 @Inject public void inject(@Named("apple") AWithProvidedBy aWithProvidedBy, 655 @Named("apple") Provider<AWithProvidedBy> aWithProvidedByProvider) { 656 this.value = aWithProvidedBy; 657 this.provider = aWithProvidedByProvider; 658 } 659 } 660 661 @ImplementedBy(PlainA.class) 662 interface AWithImplementedBy {} 663 664 static class InjectsAWithImplementedBy extends Injectable { 665 @Inject public void inject(AWithImplementedBy aWithImplementedBy, 666 Provider<AWithImplementedBy> aWithImplementedByProvider) { 667 this.value = aWithImplementedBy; 668 this.provider = aWithImplementedByProvider; 669 } 670 } 671 672 static class InjectsAWithImplementedByNamedApple extends Injectable { 673 @Inject public void inject(@Named("apple") AWithImplementedBy aWithImplementedBy, 674 @Named("apple") Provider<AWithImplementedBy> aWithImplementedByProvider) { 675 this.value = aWithImplementedBy; 676 this.provider = aWithImplementedByProvider; 677 } 678 } 679 680 interface A extends AWithProvidedBy, AWithImplementedBy {} 681 682 static class InjectsA extends Injectable { 683 @Inject public void inject(A a, Provider<A> aProvider) { 684 this.value = a; 685 this.provider = aProvider; 686 } 687 } 688 689 static class PlainA implements A { 690 final int value; 691 PlainA() { 692 value = nextId.getAndIncrement(); 693 if (value < 0) { 694 throw new RuntimeException("Illegal value: " + value); 695 } 696 } 697 PlainA(int value) { 698 this.value = value; 699 } 700 public boolean equals(Object obj) { 701 return obj instanceof PlainA 702 && value == ((PlainA) obj).value; 703 } 704 public int hashCode() { 705 return value; 706 } 707 public String toString() { 708 return "PlainA#" + value; 709 } 710 } 711 712 static class PlainAProvider implements Provider<A> { 713 public A get() { 714 return new PlainA(); 715 } 716 } 717 718 static class InjectsPlainA extends Injectable { 719 @Inject public void inject(PlainA plainA, Provider<PlainA> plainAProvider) { 720 this.value = plainA; 721 this.provider = plainAProvider; 722 } 723 } 724 725 /** This scope hands out each value exactly twice */ 726 static class TwoAtATimeScope implements Scope { 727 public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped) { 728 return new Provider<T>() { 729 T instance; 730 public T get() { 731 if (instance == null) { 732 instance = unscoped.get(); 733 return instance; 734 } else { 735 T result = instance; 736 instance = null; 737 return result; 738 } 739 } 740 }; 741 } 742 } 743 744 @Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation 745 public @interface TwoAtATimeScoped {} 746 747 @TwoAtATimeScoped 748 static class ScopedA extends PlainA {} 749 750 static class InjectsScopedA extends Injectable { 751 @Inject public void inject(ScopedA scopedA, Provider<ScopedA> scopedAProvider) { 752 this.value = scopedA; 753 this.provider = scopedAProvider; 754 } 755 } 756 757 static class InjectsScopedANamedApple extends Injectable { 758 @Inject public void inject(@Named("apple") ScopedA scopedA, 759 @Named("apple") Provider<ScopedA> scopedAProvider) { 760 this.value = scopedA; 761 this.provider = scopedAProvider; 762 } 763 } 764 765 static class Injectable { 766 Object value = new Object(); 767 Provider<?> provider = Providers.of(new Object()); 768 } 769 } 770