1/** 2 * Copyright (C) 2007 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 17package com.google.inject; 18 19import static com.google.inject.Asserts.asModuleChain; 20import static com.google.inject.Asserts.assertContains; 21import static com.google.inject.Asserts.assertNotSerializable; 22import static com.google.inject.Asserts.getDeclaringSourcePart; 23import static com.google.inject.Asserts.isIncludeStackTraceOff; 24 25import com.google.common.collect.Iterables; 26import com.google.common.collect.Lists; 27import com.google.inject.name.Named; 28import com.google.inject.name.Names; 29import com.google.inject.spi.Message; 30import com.google.inject.util.Providers; 31 32import junit.framework.TestCase; 33 34import java.io.IOException; 35import java.util.Comparator; 36import java.util.Date; 37import java.util.List; 38import java.util.concurrent.Callable; 39import java.util.logging.Handler; 40import java.util.logging.LogRecord; 41import java.util.logging.Logger; 42 43/** 44 * @author crazybob@google.com (Bob Lee) 45 */ 46public class BinderTest extends TestCase { 47 48 private final Logger loggerToWatch = Logger.getLogger(Guice.class.getName()); 49 50 private final List<LogRecord> logRecords = Lists.newArrayList(); 51 private final Handler fakeHandler = new Handler() { 52 @Override 53 public void publish(LogRecord logRecord) { 54 logRecords.add(logRecord); 55 } 56 @Override 57 public void flush() {} 58 @Override 59 public void close() throws SecurityException {} 60 }; 61 62 Provider<Foo> fooProvider; 63 64 @Override protected void setUp() throws Exception { 65 super.setUp(); 66 loggerToWatch.addHandler(fakeHandler); 67 } 68 69 @Override protected void tearDown() throws Exception { 70 loggerToWatch.removeHandler(fakeHandler); 71 super.tearDown(); 72 } 73 74 public void testProviderFromBinder() { 75 Guice.createInjector(new Module() { 76 public void configure(Binder binder) { 77 fooProvider = binder.getProvider(Foo.class); 78 79 try { 80 fooProvider.get(); 81 } catch (IllegalStateException e) { /* expected */ } 82 } 83 }); 84 85 assertNotNull(fooProvider.get()); 86 } 87 88 static class Foo {} 89 90 public void testMissingBindings() { 91 try { 92 Guice.createInjector(new AbstractModule() { 93 @Override 94 public void configure() { 95 getProvider(Runnable.class); 96 bind(Comparator.class); 97 requireBinding(Key.get(new TypeLiteral<Callable<String>>() {})); 98 bind(Date.class).annotatedWith(Names.named("date")); 99 } 100 }); 101 } catch (CreationException e) { 102 assertEquals(4, e.getErrorMessages().size()); 103 String segment1 = "No implementation for " + Comparator.class.getName() + " was bound."; 104 String segment2 = "No implementation for java.util.Date annotated with @" 105 + Named.class.getName() + "(value=date) was bound."; 106 String segment3 = "No implementation for java.lang.Runnable was bound."; 107 String segment4 = " No implementation for java.util.concurrent.Callable<java.lang.String> was" 108 + " bound."; 109 String atSegment = "at " + getClass().getName(); 110 String sourceFileName = getDeclaringSourcePart(getClass()); 111 if (isIncludeStackTraceOff()) { 112 assertContains(e.getMessage(), 113 segment1, atSegment, sourceFileName, 114 segment2, atSegment, sourceFileName, 115 segment3, atSegment, sourceFileName, 116 segment4, atSegment, sourceFileName); 117 } else { 118 assertContains(e.getMessage(), 119 segment3, atSegment, sourceFileName, 120 segment1, atSegment, sourceFileName, 121 segment4, atSegment, sourceFileName, 122 segment2, atSegment, sourceFileName); 123 } 124 } 125 } 126 127 public void testMissingDependency() { 128 try { 129 Guice.createInjector(new AbstractModule() { 130 @Override 131 public void configure() { 132 bind(NeedsRunnable.class); 133 } 134 }); 135 } catch (CreationException e) { 136 assertEquals(1, e.getErrorMessages().size()); 137 assertContains(e.getMessage(), 138 "No implementation for java.lang.Runnable was bound.", 139 "for field at " + NeedsRunnable.class.getName(), ".runnable(BinderTest.java:", 140 "at " + getClass().getName(), getDeclaringSourcePart(getClass())); 141 } 142 } 143 144 static class NeedsRunnable { 145 @Inject Runnable runnable; 146 } 147 148 public void testDanglingConstantBinding() { 149 try { 150 Guice.createInjector(new AbstractModule() { 151 @Override public void configure() { 152 bindConstant(); 153 } 154 }); 155 fail(); 156 } catch (CreationException expected) { 157 assertContains(expected.getMessage(), 158 "1) Missing constant value. Please call to(...).", 159 "at " + getClass().getName()); 160 } 161 } 162 163 public void testRecursiveBinding() { 164 try { 165 Guice.createInjector(new AbstractModule() { 166 @Override public void configure() { 167 bind(Runnable.class).to(Runnable.class); 168 } 169 }); 170 fail(); 171 } catch (CreationException expected) { 172 assertContains(expected.getMessage(), 173 "1) Binding points to itself.", 174 "at " + getClass().getName(), getDeclaringSourcePart(getClass())); 175 } 176 } 177 178 public void testBindingNullConstant() { 179 try { 180 Guice.createInjector(new AbstractModule() { 181 @Override public void configure() { 182 String none = null; 183 bindConstant().annotatedWith(Names.named("nullOne")).to(none); 184 bind(String.class).annotatedWith(Names.named("nullTwo")).toInstance(none); 185 } 186 }); 187 fail(); 188 } catch (CreationException expected) { 189 assertContains(expected.getMessage(), 190 "1) Binding to null instances is not allowed. Use toProvider(Providers.of(null))", 191 "2) Binding to null instances is not allowed. Use toProvider(Providers.of(null))"); 192 } 193 } 194 195 public void testToStringOnBinderApi() { 196 try { 197 Guice.createInjector(new AbstractModule() { 198 @Override public void configure() { 199 assertEquals("Binder", binder().toString()); 200 assertEquals("Provider<java.lang.Integer>", getProvider(Integer.class).toString()); 201 assertEquals("Provider<java.util.List<java.lang.String>>", 202 getProvider(Key.get(new TypeLiteral<List<String>>() {})).toString()); 203 204 assertEquals("BindingBuilder<java.lang.Integer>", 205 bind(Integer.class).toString()); 206 assertEquals("BindingBuilder<java.lang.Integer>", 207 bind(Integer.class).annotatedWith(Names.named("a")).toString()); 208 assertEquals("ConstantBindingBuilder", bindConstant().toString()); 209 assertEquals("ConstantBindingBuilder", 210 bindConstant().annotatedWith(Names.named("b")).toString()); 211 assertEquals("AnnotatedElementBuilder", 212 binder().newPrivateBinder().expose(Integer.class).toString()); 213 } 214 }); 215 fail(); 216 } catch (CreationException ignored) { 217 } 218 } 219 220 public void testNothingIsSerializableInBinderApi() { 221 try { 222 Guice.createInjector(new AbstractModule() { 223 @Override public void configure() { 224 try { 225 assertNotSerializable(binder()); 226 assertNotSerializable(getProvider(Integer.class)); 227 assertNotSerializable(getProvider(Key.get(new TypeLiteral<List<String>>() {}))); 228 assertNotSerializable(bind(Integer.class)); 229 assertNotSerializable(bind(Integer.class).annotatedWith(Names.named("a"))); 230 assertNotSerializable(bindConstant()); 231 assertNotSerializable(bindConstant().annotatedWith(Names.named("b"))); 232 } catch (IOException e) { 233 fail(e.getMessage()); 234 } 235 } 236 }); 237 fail(); 238 } catch (CreationException ignored) { 239 } 240 } 241 242 /** 243 * Although {@code String[].class} isn't equal to {@code new 244 * GenericArrayTypeImpl(String.class)}, Guice should treat these two types 245 * interchangeably. 246 */ 247 public void testArrayTypeCanonicalization() { 248 final String[] strings = new String[] { "A" }; 249 final Integer[] integers = new Integer[] { 1 }; 250 251 Injector injector = Guice.createInjector(new AbstractModule() { 252 @Override 253 protected void configure() { 254 bind(String[].class).toInstance(strings); 255 bind(new TypeLiteral<Integer[]>() {}).toInstance(integers); 256 } 257 }); 258 259 assertSame(integers, injector.getInstance(Key.get(new TypeLiteral<Integer[]>() {}))); 260 assertSame(integers, injector.getInstance(new Key<Integer[]>() {})); 261 assertSame(integers, injector.getInstance(Integer[].class)); 262 assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {}))); 263 assertSame(strings, injector.getInstance(new Key<String[]>() {})); 264 assertSame(strings, injector.getInstance(String[].class)); 265 266 try { 267 Guice.createInjector(new AbstractModule() { 268 @Override 269 protected void configure() { 270 bind(String[].class).toInstance(new String[] { "A" }); 271 bind(new TypeLiteral<String[]>() {}).toInstance(new String[] { "B" }); 272 } 273 }); 274 fail(); 275 } catch (CreationException expected) { 276 assertContains(expected.getMessage(), 277 "1) A binding to java.lang.String[] was already configured at " + getClass().getName(), 278 "at " + getClass().getName(), getDeclaringSourcePart(getClass())); 279 assertContains(expected.getMessage(), "1 error"); 280 } 281 282 // passes because duplicates are ignored 283 injector = Guice.createInjector(new AbstractModule() { 284 @Override 285 protected void configure() { 286 bind(String[].class).toInstance(strings); 287 bind(new TypeLiteral<String[]>() {}).toInstance(strings); 288 } 289 }); 290 assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {}))); 291 assertSame(strings, injector.getInstance(new Key<String[]>() {})); 292 assertSame(strings, injector.getInstance(String[].class)); 293 } 294 295 static class ParentModule extends AbstractModule { 296 @Override protected void configure() { 297 install(new FooModule()); 298 install(new BarModule()); 299 } 300 } 301 static class FooModule extends AbstractModule { 302 @Override protected void configure() { 303 install(new ConstantModule("foo")); 304 } 305 } 306 static class BarModule extends AbstractModule { 307 @Override protected void configure() { 308 install(new ConstantModule("bar")); 309 } 310 } 311 static class ConstantModule extends AbstractModule { 312 private final String constant; 313 ConstantModule(String constant) { 314 this.constant = constant; 315 } 316 @Override protected void configure() { 317 bind(String.class).toInstance(constant); 318 } 319 } 320 321 /** 322 * Binding something to two different things should give an error. 323 */ 324 public void testSettingBindingTwice() { 325 try { 326 Guice.createInjector(new ParentModule()); 327 fail(); 328 } catch(CreationException expected) { 329 assertContains(expected.getMessage(), 330 "1) A binding to java.lang.String was already configured at " + ConstantModule.class.getName(), 331 asModuleChain(ParentModule.class, FooModule.class, ConstantModule.class), 332 "at " + ConstantModule.class.getName(), getDeclaringSourcePart(getClass()), 333 asModuleChain(ParentModule.class, BarModule.class, ConstantModule.class)); 334 assertContains(expected.getMessage(), "1 error"); 335 } 336 } 337 338 /** 339 * Binding an @ImplementedBy thing to something else should also fail. 340 */ 341 public void testSettingAtImplementedByTwice() { 342 try { 343 Guice.createInjector(new AbstractModule() { 344 @Override 345 protected void configure() { 346 bind(HasImplementedBy1.class); 347 bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {}); 348 } 349 }); 350 fail(); 351 } catch(CreationException expected) { 352 expected.printStackTrace(); 353 assertContains(expected.getMessage(), 354 "1) A binding to " + HasImplementedBy1.class.getName() 355 + " was already configured at " + getClass().getName(), 356 "at " + getClass().getName(), getDeclaringSourcePart(getClass())); 357 assertContains(expected.getMessage(), "1 error"); 358 } 359 } 360 361 /** 362 * See issue 614, Problem One 363 * https://github.com/google/guice/issues/614 364 */ 365 public void testJitDependencyDoesntBlockOtherExplicitBindings() { 366 Injector injector = Guice.createInjector(new AbstractModule() { 367 @Override 368 protected void configure() { 369 bind(HasImplementedByThatNeedsAnotherImplementedBy.class); 370 bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {}); 371 } 372 }); 373 injector.getAllBindings(); // just validate it doesn't throw. 374 // Also validate that we're using the explicit (and not @ImplementedBy) implementation 375 assertFalse(injector.getInstance(HasImplementedBy1.class) instanceof ImplementsHasImplementedBy1); 376 } 377 378 /** 379 * See issue 614, Problem Two 380 * https://github.com/google/guice/issues/id=614 381 */ 382 public void testJitDependencyCanUseExplicitDependencies() { 383 Guice.createInjector(new AbstractModule() { 384 @Override 385 protected void configure() { 386 bind(HasImplementedByThatWantsExplicit.class); 387 bind(JustAnInterface.class).toInstance(new JustAnInterface() {}); 388 } 389 }); 390 } 391 392 /** 393 * Untargetted bindings should follow @ImplementedBy and @ProvidedBy 394 * annotations if they exist. Otherwise the class should be constructed 395 * directly. 396 */ 397 public void testUntargettedBinding() { 398 Injector injector = Guice.createInjector(new AbstractModule() { 399 @Override 400 protected void configure() { 401 bind(HasProvidedBy1.class); 402 bind(HasImplementedBy1.class); 403 bind(HasProvidedBy2.class); 404 bind(HasImplementedBy2.class); 405 bind(JustAClass.class); 406 } 407 }); 408 409 assertNotNull(injector.getInstance(HasProvidedBy1.class)); 410 assertNotNull(injector.getInstance(HasImplementedBy1.class)); 411 assertNotSame(HasProvidedBy2.class, 412 injector.getInstance(HasProvidedBy2.class).getClass()); 413 assertSame(ExtendsHasImplementedBy2.class, 414 injector.getInstance(HasImplementedBy2.class).getClass()); 415 assertSame(JustAClass.class, injector.getInstance(JustAClass.class).getClass()); 416 } 417 418 public void testPartialInjectorGetInstance() { 419 Injector injector = Guice.createInjector(); 420 try { 421 injector.getInstance(MissingParameter.class); 422 fail(); 423 } catch (ConfigurationException expected) { 424 assertContains(expected.getMessage(), 425 "1) Could not find a suitable constructor in " + NoInjectConstructor.class.getName(), 426 "at " + MissingParameter.class.getName() + ".<init>(BinderTest.java:"); 427 } 428 } 429 430 public void testUserReportedError() { 431 final Message message = new Message(getClass(), "Whoops!"); 432 try { 433 Guice.createInjector(new AbstractModule() { 434 @Override 435 protected void configure() { 436 addError(message); 437 } 438 }); 439 fail(); 440 } catch (CreationException expected) { 441 assertSame(message, Iterables.getOnlyElement(expected.getErrorMessages())); 442 } 443 } 444 445 public void testUserReportedErrorsAreAlsoLogged() { 446 try { 447 Guice.createInjector(new AbstractModule() { 448 @Override 449 protected void configure() { 450 addError(new Message("Whoops!", new IllegalArgumentException())); 451 } 452 }); 453 fail(); 454 } catch (CreationException expected) { 455 } 456 457 LogRecord logRecord = Iterables.getOnlyElement(this.logRecords); 458 assertContains(logRecord.getMessage(), 459 "An exception was caught and reported. Message: java.lang.IllegalArgumentException"); 460 } 461 462 public void testBindingToProvider() { 463 try { 464 Guice.createInjector(new AbstractModule() { 465 @Override 466 protected void configure() { 467 bind(new TypeLiteral<Provider<String>>() {}).toInstance(Providers.of("A")); 468 } 469 }); 470 fail(); 471 } catch (CreationException expected) { 472 assertContains(expected.getMessage(), 473 "1) Binding to Provider is not allowed.", 474 "at " + BinderTest.class.getName(), getDeclaringSourcePart(getClass())); 475 } 476 } 477 478 static class OuterCoreModule extends AbstractModule { 479 @Override protected void configure() { 480 install(new InnerCoreModule()); 481 } 482 } 483 static class InnerCoreModule extends AbstractModule { 484 final Named red = Names.named("red"); 485 486 @Override protected void configure() { 487 bind(AbstractModule.class).annotatedWith(red) 488 .toProvider(Providers.<AbstractModule>of(null)); 489 bind(Binder.class).annotatedWith(red).toProvider(Providers.<Binder>of(null)); 490 bind(Binding.class).annotatedWith(red).toProvider(Providers.<Binding>of(null)); 491 bind(Injector.class).annotatedWith(red).toProvider(Providers.<Injector>of(null)); 492 bind(Key.class).annotatedWith(red).toProvider(Providers.<Key>of(null)); 493 bind(Module.class).annotatedWith(red).toProvider(Providers.<Module>of(null)); 494 bind(Provider.class).annotatedWith(red).toProvider(Providers.<Provider>of(null)); 495 bind(Scope.class).annotatedWith(red).toProvider(Providers.<Scope>of(null)); 496 bind(Stage.class).annotatedWith(red).toProvider(Providers.<Stage>of(null)); 497 bind(TypeLiteral.class).annotatedWith(red).toProvider(Providers.<TypeLiteral>of(null)); 498 bind(new TypeLiteral<Key<String>>() {}).toProvider(Providers.<Key<String>>of(null)); 499 } 500 } 501 public void testCannotBindToGuiceTypes() { 502 try { 503 Guice.createInjector(new OuterCoreModule()); 504 fail(); 505 } catch (CreationException expected) { 506 assertContains(expected.getMessage(), 507 "Binding to core guice framework type is not allowed: AbstractModule.", 508 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 509 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 510 511 "Binding to core guice framework type is not allowed: Binder.", 512 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 513 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 514 515 "Binding to core guice framework type is not allowed: Binding.", 516 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 517 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 518 519 "Binding to core guice framework type is not allowed: Injector.", 520 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 521 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 522 523 "Binding to core guice framework type is not allowed: Key.", 524 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 525 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 526 527 "Binding to core guice framework type is not allowed: Module.", 528 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 529 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 530 531 "Binding to Provider is not allowed.", 532 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 533 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 534 535 "Binding to core guice framework type is not allowed: Scope.", 536 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 537 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 538 539 "Binding to core guice framework type is not allowed: Stage.", 540 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 541 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 542 543 "Binding to core guice framework type is not allowed: TypeLiteral.", 544 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 545 asModuleChain(OuterCoreModule.class, InnerCoreModule.class), 546 547 "Binding to core guice framework type is not allowed: Key.", 548 "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()), 549 asModuleChain(OuterCoreModule.class, InnerCoreModule.class)); 550 } 551 } 552 553 static class MissingParameter { 554 @Inject MissingParameter(NoInjectConstructor noInjectConstructor) {} 555 } 556 557 static class NoInjectConstructor { 558 private NoInjectConstructor() {} 559 } 560 561 @ProvidedBy(HasProvidedBy1Provider.class) 562 interface HasProvidedBy1 {} 563 564 static class HasProvidedBy1Provider implements Provider<HasProvidedBy1> { 565 public HasProvidedBy1 get() { 566 return new HasProvidedBy1() {}; 567 } 568 } 569 570 @ImplementedBy(ImplementsHasImplementedBy1.class) 571 interface HasImplementedBy1 {} 572 573 static class ImplementsHasImplementedBy1 implements HasImplementedBy1 {} 574 575 @ProvidedBy(HasProvidedBy2Provider.class) 576 static class HasProvidedBy2 {} 577 578 static class HasProvidedBy2Provider implements Provider<HasProvidedBy2> { 579 public HasProvidedBy2 get() { 580 return new HasProvidedBy2() {}; 581 } 582 } 583 584 @ImplementedBy(ExtendsHasImplementedBy2.class) 585 static class HasImplementedBy2 {} 586 587 static class ExtendsHasImplementedBy2 extends HasImplementedBy2 {} 588 589 static class JustAClass {} 590 591 @ImplementedBy(ImplementsHasImplementedByThatNeedsAnotherImplementedBy.class) 592 static interface HasImplementedByThatNeedsAnotherImplementedBy { 593 } 594 595 static class ImplementsHasImplementedByThatNeedsAnotherImplementedBy 596 implements HasImplementedByThatNeedsAnotherImplementedBy { 597 @Inject 598 ImplementsHasImplementedByThatNeedsAnotherImplementedBy( 599 HasImplementedBy1 h1n1) {} 600 } 601 602 @ImplementedBy(ImplementsHasImplementedByThatWantsExplicit.class) 603 static interface HasImplementedByThatWantsExplicit { 604 } 605 606 static class ImplementsHasImplementedByThatWantsExplicit 607 implements HasImplementedByThatWantsExplicit { 608 @Inject ImplementsHasImplementedByThatWantsExplicit(JustAnInterface jai) {} 609 } 610 611 static interface JustAnInterface {} 612 613 614// public void testBindInterfaceWithoutImplementation() { 615// Guice.createInjector(new AbstractModule() { 616// protected void configure() { 617// bind(Runnable.class); 618// } 619// }).getInstance(Runnable.class); 620// } 621 622 enum Roshambo { ROCK, SCISSORS, PAPER } 623 624 public void testInjectRawProvider() { 625 try { 626 Guice.createInjector().getInstance(Provider.class); 627 fail(); 628 } catch (ConfigurationException expected) { 629 Asserts.assertContains(expected.getMessage(), 630 "1) Cannot inject a Provider that has no type parameter", 631 "while locating " + Provider.class.getName()); 632 } 633 } 634} 635