/** * Copyright (C) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject; import static com.google.inject.Asserts.assertContains; import com.google.inject.internal.util.ImmutableSet; import com.google.inject.name.Named; import com.google.inject.name.Names; import static com.google.inject.name.Names.named; import com.google.inject.spi.Dependency; import com.google.inject.spi.ExposedBinding; import com.google.inject.spi.PrivateElements; import junit.framework.TestCase; /** * @author jessewilson@google.com (Jesse Wilson) */ public class PrivateModuleTest extends TestCase { public void testBasicUsage() { Injector injector = Guice.createInjector(new AbstractModule() { protected void configure() { bind(String.class).annotatedWith(named("a")).toInstance("public"); install(new PrivateModule() { public void configure() { bind(String.class).annotatedWith(named("b")).toInstance("i"); bind(AB.class).annotatedWith(named("one")).to(AB.class); expose(AB.class).annotatedWith(named("one")); } }); install(new PrivateModule() { public void configure() { bind(String.class).annotatedWith(named("b")).toInstance("ii"); bind(AB.class).annotatedWith(named("two")).to(AB.class); expose(AB.class).annotatedWith(named("two")); } }); } }); AB ab1 = injector.getInstance(Key.get(AB.class, named("one"))); assertEquals("public", ab1.a); assertEquals("i", ab1.b); AB ab2 = injector.getInstance(Key.get(AB.class, named("two"))); assertEquals("public", ab2.a); assertEquals("ii", ab2.b); } public void testWithoutPrivateModules() { Injector injector = Guice.createInjector(new AbstractModule() { protected void configure() { PrivateBinder bindA = binder().newPrivateBinder(); bindA.bind(String.class).annotatedWith(named("a")).toInstance("i"); bindA.expose(String.class).annotatedWith(named("a")); bindA.bind(String.class).annotatedWith(named("c")).toInstance("private to A"); PrivateBinder bindB = binder().newPrivateBinder(); bindB.bind(String.class).annotatedWith(named("b")).toInstance("ii"); bindB.expose(String.class).annotatedWith(named("b")); bindB.bind(String.class).annotatedWith(named("c")).toInstance("private to B"); } }); assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); assertEquals("ii", injector.getInstance(Key.get(String.class, named("b")))); } public void testMisplacedExposedAnnotation() { try { Guice.createInjector(new AbstractModule() { protected void configure() {} @Provides @Exposed String provideString() { return "i"; } }); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ", "Exposed bindings are only applicable to private binders.", " at " + PrivateModuleTest.class.getName(), "provideString(PrivateModuleTest.java:"); } } public void testMisplacedExposeStatement() { try { Guice.createInjector(new AbstractModule() { protected void configure() { ((PrivateBinder) binder()).expose(String.class).annotatedWith(named("a")); } }); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "Cannot expose java.lang.String on a standard binder. ", "Exposed bindings are only applicable to private binders.", " at " + PrivateModuleTest.class.getName(), "configure(PrivateModuleTest.java:"); } } public void testPrivateModulesAndProvidesMethods() { Injector injector = Guice.createInjector(new AbstractModule() { protected void configure() { install(new PrivateModule() { public void configure() { expose(String.class).annotatedWith(named("a")); } @Provides @Named("a") String providePublicA() { return "i"; } @Provides @Named("b") String providePrivateB() { return "private"; } }); install(new PrivateModule() { public void configure() {} @Provides @Named("c") String providePrivateC() { return "private"; } @Provides @Exposed @Named("d") String providePublicD() { return "ii"; } }); } }); assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); try { injector.getInstance(Key.get(String.class, named("b"))); fail(); } catch(ConfigurationException expected) { } try { injector.getInstance(Key.get(String.class, named("c"))); fail(); } catch(ConfigurationException expected) { } assertEquals("ii", injector.getInstance(Key.get(String.class, named("d")))); } public void testCannotBindAKeyExportedByASibling() { try { Guice.createInjector(new AbstractModule() { protected void configure() { install(new PrivateModule() { public void configure() { bind(String.class).toInstance("public"); expose(String.class); } }); install(new PrivateModule() { public void configure() { bind(String.class).toInstance("private"); } }); } }); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "A binding to java.lang.String was already configured at ", getClass().getName(), ".configure(PrivateModuleTest.java:", " at " + getClass().getName(), ".configure(PrivateModuleTest.java:"); } } public void testExposeButNoBind() { try { Guice.createInjector(new AbstractModule() { protected void configure() { bind(String.class).annotatedWith(named("a")).toInstance("a"); bind(String.class).annotatedWith(named("b")).toInstance("b"); install(new PrivateModule() { public void configure() { expose(AB.class); } }); } }); fail("AB was exposed but not bound"); } catch (CreationException expected) { assertContains(expected.getMessage(), "Could not expose() " + AB.class.getName() + ", it must be explicitly bound", ".configure(PrivateModuleTest.java:"); } } /** * Ensure that when we've got errors in different private modules, Guice presents all errors * in a unified message. */ public void testMessagesFromPrivateModulesAreNicelyIntegrated() { try { Guice.createInjector( new PrivateModule() { public void configure() { bind(C.class); } }, new PrivateModule() { public void configure() { bind(AB.class); } } ); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "1) No implementation for " + C.class.getName() + " was bound.", "at " + getClass().getName(), ".configure(PrivateModuleTest.java:", "2) No implementation for " + String.class.getName(), "Named(value=a) was bound.", "for field at " + AB.class.getName() + ".a(PrivateModuleTest.java:", "3) No implementation for " + String.class.getName(), "Named(value=b) was bound.", "for field at " + AB.class.getName() + ".b(PrivateModuleTest.java:", "3 errors"); } } public void testNestedPrivateInjectors() { Injector injector = Guice.createInjector(new PrivateModule() { public void configure() { expose(String.class); install(new PrivateModule() { public void configure() { bind(String.class).toInstance("nested"); expose(String.class); } }); } }); assertEquals("nested", injector.getInstance(String.class)); } public void testInstallingRegularModulesFromPrivateModules() { Injector injector = Guice.createInjector(new PrivateModule() { public void configure() { expose(String.class); install(new AbstractModule() { protected void configure() { bind(String.class).toInstance("nested"); } }); } }); assertEquals("nested", injector.getInstance(String.class)); } public void testNestedPrivateModulesWithSomeKeysUnexposed() { Injector injector = Guice.createInjector(new PrivateModule() { public void configure() { bind(String.class).annotatedWith(named("bound outer, exposed outer")).toInstance("boeo"); expose(String.class).annotatedWith(named("bound outer, exposed outer")); bind(String.class).annotatedWith(named("bound outer, exposed none")).toInstance("boen"); expose(String.class).annotatedWith(named("bound inner, exposed both")); install(new PrivateModule() { public void configure() { bind(String.class).annotatedWith(named("bound inner, exposed both")).toInstance("bieb"); expose(String.class).annotatedWith(named("bound inner, exposed both")); bind(String.class).annotatedWith(named("bound inner, exposed none")).toInstance("bien"); } }); } }); assertEquals("boeo", injector.getInstance(Key.get(String.class, named("bound outer, exposed outer")))); assertEquals("bieb", injector.getInstance(Key.get(String.class, named("bound inner, exposed both")))); try { injector.getInstance(Key.get(String.class, named("bound outer, exposed none"))); fail(); } catch (ConfigurationException expected) { } try { injector.getInstance(Key.get(String.class, named("bound inner, exposed none"))); fail(); } catch (ConfigurationException expected) { } } public void testDependenciesBetweenPrivateAndPublic() { Injector injector = Guice.createInjector( new PrivateModule() { protected void configure() {} @Provides @Exposed @Named("a") String provideA() { return "A"; } @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { return ab + "C"; } }, new AbstractModule() { protected void configure() {} @Provides @Named("ab") String provideAb(@Named("a") String a) { return a + "B"; } @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { return abc + "D"; } } ); assertEquals("ABCD", injector.getInstance(Key.get(String.class, named("abcd")))); } public void testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton() { Injector injector = Guice.createInjector( new PrivateModule() { protected void configure() {} @Provides @Exposed @Named("a") String provideA() { return "A"; } @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { return ab + "C"; } }, new AbstractModule() { protected void configure() { bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider<String>() { @Inject @Named("abcd") String abcd; public String get() { return abcd + "E"; } }).asEagerSingleton(); } @Provides @Named("ab") String provideAb(@Named("a") String a) { return a + "B"; } @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { return abc + "D"; } } ); assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); } public void testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton() { Injector injector = Guice.createInjector( new AbstractModule() { protected void configure() {} @Provides @Named("ab") String provideAb(@Named("a") String a) { return a + "B"; } @Provides @Named("abcd") String provideAbcd(@Named("abc") String abc) { return abc + "D"; } }, new PrivateModule() { protected void configure() { bind(String.class).annotatedWith(named("abcde")).toProvider(new Provider<String>() { @Inject @Named("abcd") String abcd; public String get() { return abcd + "E"; } }).asEagerSingleton(); expose(String.class).annotatedWith(named("abcde")); } @Provides @Exposed @Named("a") String provideA() { return "A"; } @Provides @Exposed @Named("abc") String provideAbc(@Named("ab") String ab) { return ab + "C"; } } ); assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); } static class AB { @Inject @Named("a") String a; @Inject @Named("b") String b; } interface C {} public void testSpiAccess() { Injector injector = Guice.createInjector(new PrivateModule() { public void configure() { bind(String.class).annotatedWith(named("a")).toInstance("private"); bind(String.class).annotatedWith(named("b")).toInstance("exposed"); expose(String.class).annotatedWith(named("b")); } }); ExposedBinding<?> binding = (ExposedBinding<?>) injector.getBinding(Key.get(String.class, Names.named("b"))); assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))), binding.getDependencies()); PrivateElements privateElements = binding.getPrivateElements(); assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class, named("b"))), privateElements.getExposedKeys()); assertContains(privateElements.getExposedSource(Key.get(String.class, named("b"))).toString(), PrivateModuleTest.class.getName(), ".configure(PrivateModuleTest.java:"); Injector privateInjector = privateElements.getInjector(); assertEquals("private", privateInjector.getInstance(Key.get(String.class, Names.named("a")))); } }