Home | History | Annotate | Download | only in inject
      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 
     17 package com.google.inject;
     18 
     19 import static com.google.inject.Asserts.assertContains;
     20 import static com.google.inject.name.Names.named;
     21 
     22 import com.google.common.collect.ImmutableSet;
     23 import com.google.common.collect.Sets;
     24 import com.google.common.util.concurrent.Runnables;
     25 import com.google.inject.matcher.Matchers;
     26 import com.google.inject.spi.InjectionPoint;
     27 import com.google.inject.spi.TypeEncounter;
     28 import com.google.inject.spi.TypeListener;
     29 
     30 import junit.framework.TestCase;
     31 
     32 /*if[AOP]*/
     33 import org.aopalliance.intercept.MethodInterceptor;
     34 import org.aopalliance.intercept.MethodInvocation;
     35 /*end[AOP]*/
     36 
     37 import java.lang.reflect.Constructor;
     38 import java.util.Collection;
     39 import java.util.List;
     40 import java.util.Map;
     41 import java.util.Set;
     42 import java.util.concurrent.atomic.AtomicInteger;
     43 import java.util.logging.Logger;
     44 
     45 /**
     46  * @author crazybob (at) google.com (Bob Lee)
     47  */
     48 public class BindingTest extends TestCase {
     49 
     50   static class Dependent {
     51     @Inject A a;
     52     @Inject Dependent(A a, B b) {}
     53     @Inject void injectBob(Bob bob) {}
     54   }
     55 
     56   public void testExplicitCyclicDependency() {
     57     Guice.createInjector(new AbstractModule() {
     58       protected void configure() {
     59         bind(A.class);
     60         bind(B.class);
     61       }
     62     }).getInstance(A.class);
     63   }
     64 
     65   static class A { @Inject B b; }
     66   static class B { @Inject A a; }
     67 
     68   static class Bob {}
     69 
     70   static class MyModule extends AbstractModule {
     71 
     72     protected void configure() {
     73       // Linked.
     74       bind(Object.class).to(Runnable.class).in(Scopes.SINGLETON);
     75 
     76       // Instance.
     77       bind(Runnable.class).toInstance(Runnables.doNothing());
     78 
     79       // Provider instance.
     80       bind(Foo.class).toProvider(new Provider<Foo>() {
     81         public Foo get() {
     82           return new Foo();
     83         }
     84       }).in(Scopes.SINGLETON);
     85 
     86       // Provider.
     87       bind(Foo.class)
     88           .annotatedWith(named("provider"))
     89           .toProvider(FooProvider.class);
     90 
     91       // Class.
     92       bind(Bar.class).in(Scopes.SINGLETON);
     93 
     94       // Constant.
     95       bindConstant().annotatedWith(named("name")).to("Bob");
     96     }
     97   }
     98 
     99   static class Foo {}
    100 
    101   public static class FooProvider implements Provider<Foo> {
    102     public Foo get() {
    103       throw new UnsupportedOperationException();
    104     }
    105   }
    106 
    107   public static class Bar {}
    108 
    109   public void testBindToUnboundLinkedBinding() {
    110     try {
    111       Guice.createInjector(new AbstractModule() {
    112         protected void configure() {
    113           bind(Collection.class).to(List.class);
    114         }
    115       });
    116       fail();
    117     } catch (CreationException expected) {
    118       assertContains(expected.getMessage(), "No implementation for java.util.List was bound.");
    119     }
    120   }
    121 
    122   /**
    123    * This test ensures that the asEagerSingleton() scoping applies to the key,
    124    * not to what the key is linked to.
    125    */
    126   public void testScopeIsAppliedToKeyNotTarget() {
    127     Injector injector = Guice.createInjector(new AbstractModule() {
    128       protected void configure() {
    129         bind(Integer.class).toProvider(Counter.class).asEagerSingleton();
    130         bind(Number.class).toProvider(Counter.class).asEagerSingleton();
    131       }
    132     });
    133 
    134     assertNotSame(injector.getInstance(Integer.class), injector.getInstance(Number.class));
    135   }
    136 
    137   static class Counter implements Provider<Integer> {
    138     static AtomicInteger next = new AtomicInteger(1);
    139     public Integer get() {
    140       return next.getAndIncrement();
    141     }
    142   }
    143 
    144   public void testAnnotatedNoArgConstructor() {
    145     assertBindingSucceeds(PublicNoArgAnnotated.class);
    146     assertBindingSucceeds(ProtectedNoArgAnnotated.class);
    147     assertBindingSucceeds(PackagePrivateNoArgAnnotated.class);
    148     assertBindingSucceeds(PrivateNoArgAnnotated.class);
    149   }
    150 
    151   static class PublicNoArgAnnotated {
    152     @Inject public PublicNoArgAnnotated() { }
    153   }
    154 
    155   static class ProtectedNoArgAnnotated {
    156     @Inject protected ProtectedNoArgAnnotated() { }
    157   }
    158 
    159   static class PackagePrivateNoArgAnnotated {
    160     @Inject PackagePrivateNoArgAnnotated() { }
    161   }
    162 
    163   static class PrivateNoArgAnnotated {
    164     @Inject private PrivateNoArgAnnotated() { }
    165   }
    166 
    167   public void testUnannotatedNoArgConstructor() throws Exception{
    168     assertBindingSucceeds(PublicNoArg.class);
    169     assertBindingSucceeds(ProtectedNoArg.class);
    170     assertBindingSucceeds(PackagePrivateNoArg.class);
    171     assertBindingSucceeds(PrivateNoArgInPrivateClass.class);
    172     assertBindingFails(PrivateNoArg.class);
    173   }
    174 
    175   static class PublicNoArg {
    176     public PublicNoArg() { }
    177   }
    178 
    179   static class ProtectedNoArg {
    180     protected ProtectedNoArg() { }
    181   }
    182 
    183   static class PackagePrivateNoArg {
    184     PackagePrivateNoArg() { }
    185   }
    186 
    187   private static class PrivateNoArgInPrivateClass {
    188     PrivateNoArgInPrivateClass() { }
    189   }
    190 
    191   static class PrivateNoArg {
    192     private PrivateNoArg() { }
    193   }
    194 
    195   private void assertBindingSucceeds(final Class<?> clazz) {
    196     assertNotNull(Guice.createInjector().getInstance(clazz));
    197   }
    198 
    199   private void assertBindingFails(final Class<?> clazz) throws NoSuchMethodException {
    200     try {
    201       Guice.createInjector().getInstance(clazz);
    202       fail();
    203     } catch (ConfigurationException expected) {
    204       assertContains(expected.getMessage(),
    205           "Could not find a suitable constructor in " + PrivateNoArg.class.getName(),
    206           "at " + PrivateNoArg.class.getName() + ".class(BindingTest.java:");
    207     }
    208   }
    209 
    210   public void testTooManyConstructors() {
    211     try {
    212       Guice.createInjector().getInstance(TooManyConstructors.class);
    213       fail();
    214     } catch (ConfigurationException expected) {
    215       assertContains(expected.getMessage(),
    216           TooManyConstructors.class.getName() + " has more than one constructor annotated with "
    217               + "@Inject. Classes must have either one (and only one) constructor",
    218           "at " + TooManyConstructors.class.getName() + ".class(BindingTest.java:");
    219     }
    220   }
    221 
    222   static class TooManyConstructors {
    223     @Inject TooManyConstructors(Injector i) {}
    224     @Inject TooManyConstructors() {}
    225   }
    226 
    227   public void testToConstructorBinding() throws NoSuchMethodException {
    228     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
    229 
    230     Injector injector = Guice.createInjector(new AbstractModule() {
    231       protected void configure() {
    232         bind(Object.class).toConstructor(constructor);
    233       }
    234     });
    235 
    236     D d = (D) injector.getInstance(Object.class);
    237     assertEquals(Stage.DEVELOPMENT, d.stage);
    238   }
    239 
    240   public void testToConstructorBindingsOnParameterizedTypes() throws NoSuchMethodException {
    241     final Constructor<C> constructor = C.class.getConstructor(Stage.class, Object.class);
    242     final Key<Object> s = new Key<Object>(named("s")) {};
    243     final Key<Object> i = new Key<Object>(named("i")) {};
    244 
    245     Injector injector = Guice.createInjector(new AbstractModule() {
    246       protected void configure() {
    247         bind(s).toConstructor(constructor, new TypeLiteral<C<Stage>>() {});
    248         bind(i).toConstructor(constructor, new TypeLiteral<C<Injector>>() {});
    249       }
    250     });
    251 
    252     C<Stage> one = (C<Stage>) injector.getInstance(s);
    253     assertEquals(Stage.DEVELOPMENT, one.stage);
    254     assertEquals(Stage.DEVELOPMENT, one.t);
    255     assertEquals(Stage.DEVELOPMENT, one.anotherT);
    256 
    257     C<Injector> two = (C<Injector>) injector.getInstance(i);
    258     assertEquals(Stage.DEVELOPMENT, two.stage);
    259     assertEquals(injector, two.t);
    260     assertEquals(injector, two.anotherT);
    261   }
    262 
    263   public void testToConstructorBindingsFailsOnRawTypes() throws NoSuchMethodException {
    264     final Constructor constructor = C.class.getConstructor(Stage.class, Object.class);
    265 
    266     try {
    267       Guice.createInjector(new AbstractModule() {
    268         protected void configure() {
    269           bind(Object.class).toConstructor(constructor);
    270         }
    271       });
    272       fail();
    273     } catch (CreationException expected) {
    274       assertContains(expected.getMessage(),
    275           "1) T cannot be used as a key; It is not fully specified.",
    276           "at " + C.class.getName() + ".<init>(BindingTest.java:",
    277           "2) T cannot be used as a key; It is not fully specified.",
    278           "at " + C.class.getName() + ".anotherT(BindingTest.java:");
    279     }
    280   }
    281 
    282 /*if[AOP]*/
    283   public void testToConstructorAndMethodInterceptors() throws NoSuchMethodException {
    284     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
    285     final AtomicInteger count = new AtomicInteger();
    286     final MethodInterceptor countingInterceptor = new MethodInterceptor() {
    287       public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    288         count.incrementAndGet();
    289         return methodInvocation.proceed();
    290       }
    291     };
    292 
    293     Injector injector = Guice.createInjector(new AbstractModule() {
    294       protected void configure() {
    295         bind(Object.class).toConstructor(constructor);
    296         bindInterceptor(Matchers.any(), Matchers.any(), countingInterceptor);
    297       }
    298     });
    299 
    300     D d = (D) injector.getInstance(Object.class);
    301     d.hashCode();
    302     d.hashCode();
    303     assertEquals(2, count.get());
    304   }
    305 /*end[AOP]*/
    306 
    307   public void testInaccessibleConstructor() throws NoSuchMethodException {
    308     final Constructor<E> constructor = E.class.getDeclaredConstructor(Stage.class);
    309 
    310     Injector injector = Guice.createInjector(new AbstractModule() {
    311       protected void configure() {
    312         bind(E.class).toConstructor(constructor);
    313       }
    314     });
    315 
    316     E e = injector.getInstance(E.class);
    317     assertEquals(Stage.DEVELOPMENT, e.stage);
    318   }
    319 
    320   public void testToConstructorAndScopes() throws NoSuchMethodException {
    321     final Constructor<F> constructor = F.class.getConstructor(Stage.class);
    322 
    323     final Key<Object> d = Key.get(Object.class, named("D")); // default scoping
    324     final Key<Object> s = Key.get(Object.class, named("S")); // singleton
    325     final Key<Object> n = Key.get(Object.class, named("N")); // "N" instances
    326     final Key<Object> r = Key.get(Object.class, named("R")); // a regular binding
    327 
    328     Injector injector = Guice.createInjector(new AbstractModule() {
    329       protected void configure() {
    330         bind(d).toConstructor(constructor);
    331         bind(s).toConstructor(constructor).in(Singleton.class);
    332         bind(n).toConstructor(constructor).in(Scopes.NO_SCOPE);
    333         bind(r).to(F.class);
    334       }
    335     });
    336 
    337     assertDistinct(injector, 1, d, d, d, d);
    338     assertDistinct(injector, 1, s, s, s, s);
    339     assertDistinct(injector, 4, n, n, n, n);
    340     assertDistinct(injector, 1, r, r, r, r);
    341     assertDistinct(injector, 4, d, d, r, r, s, s, n);
    342   }
    343 
    344   public void assertDistinct(Injector injector, int expectedCount, Key<?>... keys) {
    345     ImmutableSet.Builder<Object> builder = ImmutableSet.builder();
    346     for (Key<?> k : keys) {
    347       builder.add(injector.getInstance(k));
    348     }
    349     assertEquals(expectedCount, builder.build().size());
    350   }
    351 
    352   public void testToConstructorSpiData() throws NoSuchMethodException {
    353     final Set<TypeLiteral<?>> heardTypes = Sets.newHashSet();
    354 
    355     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
    356     final TypeListener listener = new TypeListener() {
    357       public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
    358         if (!heardTypes.add(type)) {
    359           fail("Heard " + type + " multiple times!");
    360         }
    361       }
    362     };
    363 
    364     Guice.createInjector(new AbstractModule() {
    365       protected void configure() {
    366         bind(Object.class).toConstructor(constructor);
    367         bind(D.class).toConstructor(constructor);
    368         bindListener(Matchers.any(), listener);
    369       }
    370     });
    371 
    372     assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes);
    373   }
    374 
    375   public void testInterfaceToImplementationConstructor() throws NoSuchMethodException {
    376     final Constructor<CFoo> constructor = CFoo.class.getDeclaredConstructor();
    377 
    378     Injector injector = Guice.createInjector(new AbstractModule() {
    379       protected void configure() {
    380         bind(IFoo.class).toConstructor(constructor);
    381       }
    382     });
    383 
    384     injector.getInstance(IFoo.class);
    385   }
    386 
    387   public static interface IFoo {}
    388   public static class CFoo implements IFoo {}
    389 
    390   public void testGetAllBindings() {
    391     Injector injector = Guice.createInjector(new AbstractModule() {
    392       protected void configure() {
    393         bind(D.class).toInstance(new D(Stage.PRODUCTION));
    394         bind(Object.class).to(D.class);
    395         getProvider(new Key<C<Stage>>() {});
    396       }
    397     });
    398 
    399     Map<Key<?>,Binding<?>> bindings = injector.getAllBindings();
    400     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
    401         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
    402         bindings.keySet());
    403 
    404     // add a JIT binding
    405     injector.getInstance(F.class);
    406 
    407     Map<Key<?>,Binding<?>> bindings2 = injector.getAllBindings();
    408     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
    409         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}, Key.get(F.class)),
    410         bindings2.keySet());
    411 
    412     // the original map shouldn't have changed
    413     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
    414         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
    415         bindings.keySet());
    416 
    417     // check the bindings' values
    418     assertEquals(injector, bindings.get(Key.get(Injector.class)).getProvider().get());
    419   }
    420 
    421   public void testGetAllServletBindings() throws Exception {
    422     Injector injector = Guice.createInjector(new AbstractModule() {
    423       protected void configure() {
    424         bind(F.class); // an explicit binding that uses a JIT binding for a constructor
    425       }
    426     });
    427     injector.getAllBindings();
    428   }
    429 
    430   public static class C<T> {
    431     private Stage stage;
    432     private T t;
    433     @Inject T anotherT;
    434 
    435     public C(Stage stage, T t) {
    436       this.stage = stage;
    437       this.t = t;
    438     }
    439 
    440     @Inject C() {}
    441   }
    442 
    443   public static class D {
    444     Stage stage;
    445     public D(Stage stage) {
    446       this.stage = stage;
    447     }
    448   }
    449 
    450   private static class E {
    451     Stage stage;
    452     private E(Stage stage) {
    453       this.stage = stage;
    454     }
    455   }
    456 
    457   @Singleton
    458   public static class F {
    459     Stage stage;
    460     @Inject public F(Stage stage) {
    461       this.stage = stage;
    462     }
    463   }
    464 
    465   public void testTurkeyBaconProblemUsingToConstuctor() {
    466     Injector injector = Guice.createInjector(new AbstractModule() {
    467       @SuppressWarnings("unchecked")
    468       @Override
    469       public void configure() {
    470         bind(Bacon.class).to(UncookedBacon.class);
    471         bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class);
    472         bind(Bacon.class).annotatedWith(named("Cooked")).toConstructor(
    473             (Constructor)InjectionPoint.forConstructorOf(Bacon.class).getMember());
    474       }
    475     });
    476     Bacon bacon = injector.getInstance(Bacon.class);
    477     assertEquals(Food.PORK, bacon.getMaterial());
    478     assertFalse(bacon.isCooked());
    479 
    480     Bacon turkeyBacon = injector.getInstance(Key.get(Bacon.class, named("Turkey")));
    481     assertEquals(Food.TURKEY, turkeyBacon.getMaterial());
    482     assertTrue(turkeyBacon.isCooked());
    483 
    484     Bacon cookedBacon = injector.getInstance(Key.get(Bacon.class, named("Cooked")));
    485     assertEquals(Food.PORK, cookedBacon.getMaterial());
    486     assertTrue(cookedBacon.isCooked());
    487   }
    488 
    489   enum Food { TURKEY, PORK }
    490 
    491   private static class Bacon {
    492     public Food getMaterial() { return Food.PORK; }
    493     public boolean isCooked() { return true; }
    494   }
    495 
    496   private static class TurkeyBacon extends Bacon {
    497     public Food getMaterial() { return Food.TURKEY; }
    498   }
    499 
    500   private static class UncookedBacon extends Bacon {
    501     public boolean isCooked() { return false; }
    502   }
    503 }
    504