Home | History | Annotate | Download | only in inject
      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.asModuleChain;
     20 import static com.google.inject.Asserts.assertContains;
     21 import static com.google.inject.Asserts.getDeclaringSourcePart;
     22 import static com.google.inject.name.Names.named;
     23 
     24 import com.google.common.collect.ImmutableSet;
     25 import com.google.inject.name.Named;
     26 import com.google.inject.name.Names;
     27 import com.google.inject.spi.Dependency;
     28 import com.google.inject.spi.ExposedBinding;
     29 import com.google.inject.spi.PrivateElements;
     30 import com.google.inject.util.Types;
     31 
     32 import junit.framework.TestCase;
     33 
     34 import java.util.ArrayList;
     35 import java.util.Collection;
     36 import java.util.List;
     37 
     38 /**
     39  * @author jessewilson (at) google.com (Jesse Wilson)
     40  */
     41 public class PrivateModuleTest extends TestCase {
     42 
     43   public void testBasicUsage() {
     44     Injector injector = Guice.createInjector(new AbstractModule() {
     45       @Override protected void configure() {
     46         bind(String.class).annotatedWith(named("a")).toInstance("public");
     47 
     48         install(new PrivateModule() {
     49           @Override public void configure() {
     50             bind(String.class).annotatedWith(named("b")).toInstance("i");
     51 
     52             bind(AB.class).annotatedWith(named("one")).to(AB.class);
     53             expose(AB.class).annotatedWith(named("one"));
     54           }
     55         });
     56 
     57         install(new PrivateModule() {
     58           @Override public void configure() {
     59             bind(String.class).annotatedWith(named("b")).toInstance("ii");
     60 
     61             bind(AB.class).annotatedWith(named("two")).to(AB.class);
     62             expose(AB.class).annotatedWith(named("two"));
     63           }
     64         });
     65       }
     66     });
     67 
     68     AB ab1 = injector.getInstance(Key.get(AB.class, named("one")));
     69     assertEquals("public", ab1.a);
     70     assertEquals("i", ab1.b);
     71 
     72     AB ab2 = injector.getInstance(Key.get(AB.class, named("two")));
     73     assertEquals("public", ab2.a);
     74     assertEquals("ii", ab2.b);
     75   }
     76 
     77   public void testWithoutPrivateModules() {
     78     Injector injector = Guice.createInjector(new AbstractModule() {
     79       @Override protected void configure() {
     80         PrivateBinder bindA = binder().newPrivateBinder();
     81         bindA.bind(String.class).annotatedWith(named("a")).toInstance("i");
     82         bindA.expose(String.class).annotatedWith(named("a"));
     83         bindA.bind(String.class).annotatedWith(named("c")).toInstance("private to A");
     84 
     85         PrivateBinder bindB = binder().newPrivateBinder();
     86         bindB.bind(String.class).annotatedWith(named("b")).toInstance("ii");
     87         bindB.expose(String.class).annotatedWith(named("b"));
     88         bindB.bind(String.class).annotatedWith(named("c")).toInstance("private to B");
     89       }
     90     });
     91 
     92     assertEquals("i", injector.getInstance(Key.get(String.class, named("a"))));
     93     assertEquals("ii", injector.getInstance(Key.get(String.class, named("b"))));
     94   }
     95 
     96   public void testMisplacedExposedAnnotation() {
     97     try {
     98       Guice.createInjector(new AbstractModule() {
     99         @Override protected void configure() {}
    100 
    101         @Provides @Exposed
    102         String provideString() {
    103           return "i";
    104         }
    105       });
    106       fail();
    107     } catch (CreationException expected) {
    108       assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ",
    109           "Exposed bindings are only applicable to private binders.",
    110           " at " + PrivateModuleTest.class.getName(), "provideString(PrivateModuleTest.java:");
    111     }
    112   }
    113 
    114   public void testMisplacedExposeStatement() {
    115     try {
    116       Guice.createInjector(new AbstractModule() {
    117         @Override protected void configure() {
    118           ((PrivateBinder) binder()).expose(String.class).annotatedWith(named("a"));
    119         }
    120       });
    121       fail();
    122     } catch (CreationException expected) {
    123       assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ",
    124           "Exposed bindings are only applicable to private binders.",
    125           " at " + PrivateModuleTest.class.getName(), getDeclaringSourcePart(getClass()));
    126     }
    127   }
    128 
    129   public void testPrivateModulesAndProvidesMethods() {
    130     Injector injector = Guice.createInjector(new AbstractModule() {
    131       @Override protected void configure() {
    132         install(new PrivateModule() {
    133           @Override public void configure() {
    134             expose(String.class).annotatedWith(named("a"));
    135           }
    136 
    137           @Provides @Named("a") String providePublicA() {
    138             return "i";
    139           }
    140 
    141           @Provides @Named("b") String providePrivateB() {
    142             return "private";
    143           }
    144         });
    145 
    146         install(new PrivateModule() {
    147           @Override public void configure() {}
    148 
    149           @Provides @Named("c") String providePrivateC() {
    150             return "private";
    151           }
    152 
    153           @Provides @Exposed @Named("d") String providePublicD() {
    154             return "ii";
    155           }
    156         });
    157       }
    158     });
    159 
    160     assertEquals("i", injector.getInstance(Key.get(String.class, named("a"))));
    161 
    162     try {
    163       injector.getInstance(Key.get(String.class, named("b")));
    164       fail();
    165     } catch(ConfigurationException expected) {
    166     }
    167 
    168     try {
    169       injector.getInstance(Key.get(String.class, named("c")));
    170       fail();
    171     } catch(ConfigurationException expected) {
    172     }
    173 
    174     assertEquals("ii", injector.getInstance(Key.get(String.class, named("d"))));
    175   }
    176 
    177   public void testCannotBindAKeyExportedByASibling() {
    178     try {
    179       Guice.createInjector(new AbstractModule() {
    180         @Override protected void configure() {
    181           install(new PrivateModule() {
    182             @Override public void configure() {
    183               bind(String.class).toInstance("public");
    184               expose(String.class);
    185             }
    186           });
    187 
    188           install(new PrivateModule() {
    189             @Override public void configure() {
    190               bind(String.class).toInstance("private");
    191             }
    192           });
    193         }
    194       });
    195       fail();
    196     } catch (CreationException expected) {
    197       assertContains(expected.getMessage(),
    198           "A binding to java.lang.String was already configured at ",
    199           getClass().getName(), getDeclaringSourcePart(getClass()),
    200           " at " + getClass().getName(), getDeclaringSourcePart(getClass()));
    201     }
    202   }
    203 
    204   public void testExposeButNoBind() {
    205     try {
    206       Guice.createInjector(new AbstractModule() {
    207         @Override protected void configure() {
    208           bind(String.class).annotatedWith(named("a")).toInstance("a");
    209           bind(String.class).annotatedWith(named("b")).toInstance("b");
    210 
    211           install(new PrivateModule() {
    212             @Override public void configure() {
    213               expose(AB.class);
    214             }
    215           });
    216         }
    217       });
    218       fail("AB was exposed but not bound");
    219     } catch (CreationException expected) {
    220       assertContains(expected.getMessage(),
    221           "Could not expose() " + AB.class.getName() + ", it must be explicitly bound",
    222           getDeclaringSourcePart(getClass()));
    223     }
    224   }
    225 
    226   /**
    227    * Ensure that when we've got errors in different private modules, Guice presents all errors
    228    * in a unified message.
    229    */
    230   public void testMessagesFromPrivateModulesAreNicelyIntegrated() {
    231     try {
    232       Guice.createInjector(
    233           new PrivateModule() {
    234             @Override public void configure() {
    235               bind(C.class);
    236             }
    237           },
    238           new PrivateModule() {
    239             @Override public void configure() {
    240               bind(AB.class);
    241             }
    242           }
    243       );
    244       fail();
    245     } catch (CreationException expected) {
    246       assertContains(expected.getMessage(),
    247           "1) No implementation for " + C.class.getName() + " was bound.",
    248           "at " + getClass().getName(), getDeclaringSourcePart(getClass()),
    249           "2) No implementation for " + String.class.getName(), "Named(value=a) was bound.",
    250           "for field at " + AB.class.getName() + ".a(PrivateModuleTest.java:",
    251           "3) No implementation for " + String.class.getName(), "Named(value=b) was bound.",
    252           "for field at " + AB.class.getName() + ".b(PrivateModuleTest.java:",
    253           "3 errors");
    254     }
    255   }
    256 
    257   public void testNestedPrivateInjectors() {
    258     Injector injector = Guice.createInjector(new PrivateModule() {
    259       @Override public void configure() {
    260         expose(String.class);
    261 
    262         install(new PrivateModule() {
    263           @Override public void configure() {
    264             bind(String.class).toInstance("nested");
    265             expose(String.class);
    266           }
    267         });
    268       }
    269     });
    270 
    271     assertEquals("nested", injector.getInstance(String.class));
    272   }
    273 
    274   public void testInstallingRegularModulesFromPrivateModules() {
    275     Injector injector = Guice.createInjector(new PrivateModule() {
    276       @Override public void configure() {
    277         expose(String.class);
    278 
    279         install(new AbstractModule() {
    280           @Override protected void configure() {
    281             bind(String.class).toInstance("nested");
    282           }
    283         });
    284       }
    285     });
    286 
    287     assertEquals("nested", injector.getInstance(String.class));
    288   }
    289 
    290   public void testNestedPrivateModulesWithSomeKeysUnexposed() {
    291     Injector injector = Guice.createInjector(new PrivateModule() {
    292       @Override public void configure() {
    293         bind(String.class).annotatedWith(named("bound outer, exposed outer")).toInstance("boeo");
    294         expose(String.class).annotatedWith(named("bound outer, exposed outer"));
    295         bind(String.class).annotatedWith(named("bound outer, exposed none")).toInstance("boen");
    296         expose(String.class).annotatedWith(named("bound inner, exposed both"));
    297 
    298         install(new PrivateModule() {
    299           @Override public void configure() {
    300             bind(String.class).annotatedWith(named("bound inner, exposed both")).toInstance("bieb");
    301             expose(String.class).annotatedWith(named("bound inner, exposed both"));
    302             bind(String.class).annotatedWith(named("bound inner, exposed none")).toInstance("bien");
    303           }
    304         });
    305       }
    306     });
    307 
    308     assertEquals("boeo",
    309         injector.getInstance(Key.get(String.class, named("bound outer, exposed outer"))));
    310     assertEquals("bieb",
    311         injector.getInstance(Key.get(String.class, named("bound inner, exposed both"))));
    312 
    313     try {
    314       injector.getInstance(Key.get(String.class, named("bound outer, exposed none")));
    315       fail();
    316     } catch (ConfigurationException expected) {
    317     }
    318 
    319     try {
    320       injector.getInstance(Key.get(String.class, named("bound inner, exposed none")));
    321       fail();
    322     } catch (ConfigurationException expected) {
    323     }
    324   }
    325 
    326   public void testDependenciesBetweenPrivateAndPublic() {
    327     Injector injector = Guice.createInjector(
    328         new PrivateModule() {
    329           @Override protected void configure() {}
    330 
    331           @Provides @Exposed @Named("a") String provideA() {
    332             return "A";
    333           }
    334 
    335           @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) {
    336             return ab + "C";
    337           }
    338         },
    339         new AbstractModule() {
    340           @Override protected void configure() {}
    341 
    342           @Provides @Named("ab") String provideAb(@Named("a") String a) {
    343             return a + "B";
    344           }
    345 
    346           @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) {
    347             return abc + "D";
    348           }
    349         }
    350     );
    351 
    352     assertEquals("ABCD", injector.getInstance(Key.get(String.class, named("abcd"))));
    353   }
    354 
    355   public void testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton() {
    356     Injector injector = Guice.createInjector(
    357         new PrivateModule() {
    358           @Override protected void configure() {}
    359 
    360           @Provides @Exposed @Named("a") String provideA() {
    361             return "A";
    362           }
    363 
    364           @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) {
    365             return ab + "C";
    366           }
    367         },
    368         new AbstractModule() {
    369           @Override protected void configure() {
    370             bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider<String>() {
    371               @Inject @Named("abcd") String abcd;
    372 
    373               public String get() {
    374                 return abcd + "E";
    375               }
    376             }).asEagerSingleton();
    377           }
    378 
    379           @Provides @Named("ab") String provideAb(@Named("a") String a) {
    380             return a + "B";
    381           }
    382 
    383           @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) {
    384             return abc + "D";
    385           }
    386         }
    387     );
    388 
    389     assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde"))));
    390   }
    391 
    392   public void testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton() {
    393     Injector injector = Guice.createInjector(
    394         new AbstractModule() {
    395           @Override protected void configure() {}
    396 
    397           @Provides @Named("ab") String provideAb(@Named("a") String a) {
    398             return a + "B";
    399           }
    400 
    401           @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) {
    402             return abc + "D";
    403           }
    404         },
    405         new PrivateModule() {
    406           @Override protected void configure() {
    407             bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider<String>() {
    408               @Inject @Named("abcd") String abcd;
    409 
    410               public String get() {
    411                 return abcd + "E";
    412               }
    413             }).asEagerSingleton();
    414             expose(String.class).annotatedWith(named("abcde"));
    415           }
    416 
    417           @Provides @Exposed @Named("a") String provideA() {
    418             return "A";
    419           }
    420 
    421           @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) {
    422             return ab + "C";
    423           }
    424         }
    425     );
    426 
    427     assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde"))));
    428   }
    429 
    430   static class AB {
    431     @Inject @Named("a") String a;
    432     @Inject @Named("b") String b;
    433   }
    434 
    435   interface C {}
    436 
    437   public void testSpiAccess() {
    438     Injector injector = Guice.createInjector(new PrivateModule() {
    439           @Override public void configure() {
    440             bind(String.class).annotatedWith(named("a")).toInstance("private");
    441             bind(String.class).annotatedWith(named("b")).toInstance("exposed");
    442             expose(String.class).annotatedWith(named("b"));
    443           }
    444         });
    445 
    446     ExposedBinding<?> binding
    447         = (ExposedBinding<?>) injector.getBinding(Key.get(String.class, Names.named("b")));
    448     assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))),
    449         binding.getDependencies());
    450     PrivateElements privateElements = binding.getPrivateElements();
    451     assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class, named("b"))),
    452         privateElements.getExposedKeys());
    453     assertContains(privateElements.getExposedSource(Key.get(String.class, named("b"))).toString(),
    454         PrivateModuleTest.class.getName(), getDeclaringSourcePart(getClass()));
    455     Injector privateInjector = privateElements.getInjector();
    456     assertEquals("private", privateInjector.getInstance(Key.get(String.class, Names.named("a"))));
    457   }
    458 
    459   public void testParentBindsSomethingInPrivate() {
    460     try {
    461       Guice.createInjector(new FailingModule());
    462       fail();
    463     } catch(CreationException expected) {
    464       assertEquals(1, expected.getErrorMessages().size());
    465       assertContains(expected.toString(),
    466           "Unable to create binding for java.util.List.",
    467           "It was already configured on one or more child injectors or private modules",
    468           "bound at " + FailingPrivateModule.class.getName() + ".configure(",
    469           asModuleChain(FailingModule.class, ManyPrivateModules.class, FailingPrivateModule.class),
    470           "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(",
    471           asModuleChain(
    472               FailingModule.class, ManyPrivateModules.class, SecondFailingPrivateModule.class),
    473           "If it was in a PrivateModule, did you forget to expose the binding?",
    474           "at " + FailingModule.class.getName() + ".configure(");
    475     }
    476   }
    477 
    478   public void testParentBindingToPrivateLinkedJitBinding() {
    479     Injector injector = Guice.createInjector(new ManyPrivateModules());
    480     try {
    481       injector.getBinding(Key.get(Types.providerOf(List.class)));
    482       fail();
    483     } catch(ConfigurationException expected) {
    484       assertEquals(1, expected.getErrorMessages().size());
    485       assertContains(expected.toString(),
    486           "Unable to create binding for com.google.inject.Provider<java.util.List>.",
    487           "It was already configured on one or more child injectors or private modules",
    488           "bound at " + FailingPrivateModule.class.getName() + ".configure(",
    489           asModuleChain(ManyPrivateModules.class, FailingPrivateModule.class),
    490           "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(",
    491           asModuleChain(ManyPrivateModules.class, SecondFailingPrivateModule.class),
    492           "If it was in a PrivateModule, did you forget to expose the binding?",
    493           "while locating com.google.inject.Provider<java.util.List>");
    494     }
    495   }
    496 
    497   public void testParentBindingToPrivateJitBinding() {
    498     Injector injector = Guice.createInjector(new ManyPrivateModules());
    499     try {
    500       injector.getBinding(PrivateFoo.class);
    501       fail();
    502     } catch(ConfigurationException expected) {
    503       assertEquals(1, expected.getErrorMessages().size());
    504       assertContains(expected.toString(),
    505           "Unable to create binding for " + PrivateFoo.class.getName(),
    506           "It was already configured on one or more child injectors or private modules",
    507           "(bound by a just-in-time binding)",
    508           "If it was in a PrivateModule, did you forget to expose the binding?",
    509           "while locating " + PrivateFoo.class.getName());
    510     }
    511   }
    512 
    513   private static class FailingModule extends AbstractModule {
    514     @Override protected void configure() {
    515       bind(Collection.class).to(List.class);
    516       install(new ManyPrivateModules());
    517     }
    518   }
    519 
    520   private static class ManyPrivateModules extends AbstractModule {
    521     @Override protected void configure() {
    522       // make sure duplicate sources are collapsed
    523       install(new FailingPrivateModule());
    524       install(new FailingPrivateModule());
    525       // but additional sources are listed
    526       install(new SecondFailingPrivateModule());
    527     }
    528   }
    529 
    530   private static class FailingPrivateModule extends PrivateModule {
    531     @Override protected void configure() {
    532       bind(List.class).toInstance(new ArrayList());
    533 
    534       // Add the Provider<List> binding, created just-in-time,
    535       // to make sure our linked JIT bindings have the correct source.
    536       getProvider(Key.get(Types.providerOf(List.class)));
    537 
    538       // Request a JIT binding for PrivateFoo, which can only
    539       // be created in the private module because it depends
    540       // on List.
    541       getProvider(PrivateFoo.class);
    542     }
    543   }
    544 
    545   /** A second class, so we can see another name in the source list. */
    546   private static class SecondFailingPrivateModule extends PrivateModule {
    547     @Override protected void configure() {
    548       bind(List.class).toInstance(new ArrayList());
    549 
    550       // Add the Provider<List> binding, created just-in-time,
    551       // to make sure our linked JIT bindings have the correct source.
    552       getProvider(Key.get(Types.providerOf(List.class)));
    553 
    554       // Request a JIT binding for PrivateFoo, which can only
    555       // be created in the private module because it depends
    556       // on List.
    557       getProvider(PrivateFoo.class);
    558     }
    559   }
    560 
    561   private static class PrivateFoo {
    562     @Inject List list;
    563   }
    564 }
    565