/**
* Copyright (C) 2007 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.asModuleChain;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.Asserts.assertNotSerializable;
import static com.google.inject.Asserts.getDeclaringSourcePart;
import static com.google.inject.Asserts.isIncludeStackTraceOff;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import junit.framework.TestCase;
import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
* @author crazybob@google.com (Bob Lee)
*/
public class BinderTest extends TestCase {
private final Logger loggerToWatch = Logger.getLogger(Guice.class.getName());
private final List<LogRecord> logRecords = Lists.newArrayList();
private final Handler fakeHandler = new Handler() {
@Override
public void publish(LogRecord logRecord) {
logRecords.add(logRecord);
}
@Override
public void flush() {}
@Override
public void close() throws SecurityException {}
};
Provider<Foo> fooProvider;
@Override protected void setUp() throws Exception {
super.setUp();
loggerToWatch.addHandler(fakeHandler);
}
@Override protected void tearDown() throws Exception {
loggerToWatch.removeHandler(fakeHandler);
super.tearDown();
}
public void testProviderFromBinder() {
Guice.createInjector(new Module() {
public void configure(Binder binder) {
fooProvider = binder.getProvider(Foo.class);
try {
fooProvider.get();
} catch (IllegalStateException e) { /* expected */ }
}
});
assertNotNull(fooProvider.get());
}
static class Foo {}
public void testMissingBindings() {
try {
Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
getProvider(Runnable.class);
bind(Comparator.class);
requireBinding(Key.get(new TypeLiteral<Callable<String>>() {}));
bind(Date.class).annotatedWith(Names.named("date"));
}
});
} catch (CreationException e) {
assertEquals(4, e.getErrorMessages().size());
String segment1 = "No implementation for " + Comparator.class.getName() + " was bound.";
String segment2 = "No implementation for java.util.Date annotated with @"
+ Named.class.getName() + "(value=date) was bound.";
String segment3 = "No implementation for java.lang.Runnable was bound.";
String segment4 = " No implementation for java.util.concurrent.Callable<java.lang.String> was"
+ " bound.";
String atSegment = "at " + getClass().getName();
String sourceFileName = getDeclaringSourcePart(getClass());
if (isIncludeStackTraceOff()) {
assertContains(e.getMessage(),
segment1, atSegment, sourceFileName,
segment2, atSegment, sourceFileName,
segment3, atSegment, sourceFileName,
segment4, atSegment, sourceFileName);
} else {
assertContains(e.getMessage(),
segment3, atSegment, sourceFileName,
segment1, atSegment, sourceFileName,
segment4, atSegment, sourceFileName,
segment2, atSegment, sourceFileName);
}
}
}
public void testMissingDependency() {
try {
Guice.createInjector(new AbstractModule() {
@Override
public void configure() {
bind(NeedsRunnable.class);
}
});
} catch (CreationException e) {
assertEquals(1, e.getErrorMessages().size());
assertContains(e.getMessage(),
"No implementation for java.lang.Runnable was bound.",
"for field at " + NeedsRunnable.class.getName(), ".runnable(BinderTest.java:",
"at " + getClass().getName(), getDeclaringSourcePart(getClass()));
}
}
static class NeedsRunnable {
@Inject Runnable runnable;
}
public void testDanglingConstantBinding() {
try {
Guice.createInjector(new AbstractModule() {
@Override public void configure() {
bindConstant();
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) Missing constant value. Please call to(...).",
"at " + getClass().getName());
}
}
public void testRecursiveBinding() {
try {
Guice.createInjector(new AbstractModule() {
@Override public void configure() {
bind(Runnable.class).to(Runnable.class);
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) Binding points to itself.",
"at " + getClass().getName(), getDeclaringSourcePart(getClass()));
}
}
public void testBindingNullConstant() {
try {
Guice.createInjector(new AbstractModule() {
@Override public void configure() {
String none = null;
bindConstant().annotatedWith(Names.named("nullOne")).to(none);
bind(String.class).annotatedWith(Names.named("nullTwo")).toInstance(none);
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) Binding to null instances is not allowed. Use toProvider(Providers.of(null))",
"2) Binding to null instances is not allowed. Use toProvider(Providers.of(null))");
}
}
public void testToStringOnBinderApi() {
try {
Guice.createInjector(new AbstractModule() {
@Override public void configure() {
assertEquals("Binder", binder().toString());
assertEquals("Provider<java.lang.Integer>", getProvider(Integer.class).toString());
assertEquals("Provider<java.util.List<java.lang.String>>",
getProvider(Key.get(new TypeLiteral<List<String>>() {})).toString());
assertEquals("BindingBuilder<java.lang.Integer>",
bind(Integer.class).toString());
assertEquals("BindingBuilder<java.lang.Integer>",
bind(Integer.class).annotatedWith(Names.named("a")).toString());
assertEquals("ConstantBindingBuilder", bindConstant().toString());
assertEquals("ConstantBindingBuilder",
bindConstant().annotatedWith(Names.named("b")).toString());
assertEquals("AnnotatedElementBuilder",
binder().newPrivateBinder().expose(Integer.class).toString());
}
});
fail();
} catch (CreationException ignored) {
}
}
public void testNothingIsSerializableInBinderApi() {
try {
Guice.createInjector(new AbstractModule() {
@Override public void configure() {
try {
assertNotSerializable(binder());
assertNotSerializable(getProvider(Integer.class));
assertNotSerializable(getProvider(Key.get(new TypeLiteral<List<String>>() {})));
assertNotSerializable(bind(Integer.class));
assertNotSerializable(bind(Integer.class).annotatedWith(Names.named("a")));
assertNotSerializable(bindConstant());
assertNotSerializable(bindConstant().annotatedWith(Names.named("b")));
} catch (IOException e) {
fail(e.getMessage());
}
}
});
fail();
} catch (CreationException ignored) {
}
}
/**
* Although {@code String[].class} isn't equal to {@code new
* GenericArrayTypeImpl(String.class)}, Guice should treat these two types
* interchangeably.
*/
public void testArrayTypeCanonicalization() {
final String[] strings = new String[] { "A" };
final Integer[] integers = new Integer[] { 1 };
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String[].class).toInstance(strings);
bind(new TypeLiteral<Integer[]>() {}).toInstance(integers);
}
});
assertSame(integers, injector.getInstance(Key.get(new TypeLiteral<Integer[]>() {})));
assertSame(integers, injector.getInstance(new Key<Integer[]>() {}));
assertSame(integers, injector.getInstance(Integer[].class));
assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {})));
assertSame(strings, injector.getInstance(new Key<String[]>() {}));
assertSame(strings, injector.getInstance(String[].class));
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String[].class).toInstance(new String[] { "A" });
bind(new TypeLiteral<String[]>() {}).toInstance(new String[] { "B" });
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) A binding to java.lang.String[] was already configured at " + getClass().getName(),
"at " + getClass().getName(), getDeclaringSourcePart(getClass()));
assertContains(expected.getMessage(), "1 error");
}
// passes because duplicates are ignored
injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String[].class).toInstance(strings);
bind(new TypeLiteral<String[]>() {}).toInstance(strings);
}
});
assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {})));
assertSame(strings, injector.getInstance(new Key<String[]>() {}));
assertSame(strings, injector.getInstance(String[].class));
}
static class ParentModule extends AbstractModule {
@Override protected void configure() {
install(new FooModule());
install(new BarModule());
}
}
static class FooModule extends AbstractModule {
@Override protected void configure() {
install(new ConstantModule("foo"));
}
}
static class BarModule extends AbstractModule {
@Override protected void configure() {
install(new ConstantModule("bar"));
}
}
static class ConstantModule extends AbstractModule {
private final String constant;
ConstantModule(String constant) {
this.constant = constant;
}
@Override protected void configure() {
bind(String.class).toInstance(constant);
}
}
/**
* Binding something to two different things should give an error.
*/
public void testSettingBindingTwice() {
try {
Guice.createInjector(new ParentModule());
fail();
} catch(CreationException expected) {
assertContains(expected.getMessage(),
"1) A binding to java.lang.String was already configured at " + ConstantModule.class.getName(),
asModuleChain(ParentModule.class, FooModule.class, ConstantModule.class),
"at " + ConstantModule.class.getName(), getDeclaringSourcePart(getClass()),
asModuleChain(ParentModule.class, BarModule.class, ConstantModule.class));
assertContains(expected.getMessage(), "1 error");
}
}
/**
* Binding an @ImplementedBy thing to something else should also fail.
*/
public void testSettingAtImplementedByTwice() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(HasImplementedBy1.class);
bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {});
}
});
fail();
} catch(CreationException expected) {
expected.printStackTrace();
assertContains(expected.getMessage(),
"1) A binding to " + HasImplementedBy1.class.getName()
+ " was already configured at " + getClass().getName(),
"at " + getClass().getName(), getDeclaringSourcePart(getClass()));
assertContains(expected.getMessage(), "1 error");
}
}
/**
* See issue 614, Problem One
* https://github.com/google/guice/issues/614
*/
public void testJitDependencyDoesntBlockOtherExplicitBindings() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(HasImplementedByThatNeedsAnotherImplementedBy.class);
bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {});
}
});
injector.getAllBindings(); // just validate it doesn't throw.
// Also validate that we're using the explicit (and not @ImplementedBy) implementation
assertFalse(injector.getInstance(HasImplementedBy1.class) instanceof ImplementsHasImplementedBy1);
}
/**
* See issue 614, Problem Two
* https://github.com/google/guice/issues/id=614
*/
public void testJitDependencyCanUseExplicitDependencies() {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(HasImplementedByThatWantsExplicit.class);
bind(JustAnInterface.class).toInstance(new JustAnInterface() {});
}
});
}
/**
* Untargetted bindings should follow @ImplementedBy and @ProvidedBy
* annotations if they exist. Otherwise the class should be constructed
* directly.
*/
public void testUntargettedBinding() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(HasProvidedBy1.class);
bind(HasImplementedBy1.class);
bind(HasProvidedBy2.class);
bind(HasImplementedBy2.class);
bind(JustAClass.class);
}
});
assertNotNull(injector.getInstance(HasProvidedBy1.class));
assertNotNull(injector.getInstance(HasImplementedBy1.class));
assertNotSame(HasProvidedBy2.class,
injector.getInstance(HasProvidedBy2.class).getClass());
assertSame(ExtendsHasImplementedBy2.class,
injector.getInstance(HasImplementedBy2.class).getClass());
assertSame(JustAClass.class, injector.getInstance(JustAClass.class).getClass());
}
public void testPartialInjectorGetInstance() {
Injector injector = Guice.createInjector();
try {
injector.getInstance(MissingParameter.class);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(),
"1) Could not find a suitable constructor in " + NoInjectConstructor.class.getName(),
"at " + MissingParameter.class.getName() + ".<init>(BinderTest.java:");
}
}
public void testUserReportedError() {
final Message message = new Message(getClass(), "Whoops!");
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
addError(message);
}
});
fail();
} catch (CreationException expected) {
assertSame(message, Iterables.getOnlyElement(expected.getErrorMessages()));
}
}
public void testUserReportedErrorsAreAlsoLogged() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
addError(new Message("Whoops!", new IllegalArgumentException()));
}
});
fail();
} catch (CreationException expected) {
}
LogRecord logRecord = Iterables.getOnlyElement(this.logRecords);
assertContains(logRecord.getMessage(),
"An exception was caught and reported. Message: java.lang.IllegalArgumentException");
}
public void testBindingToProvider() {
try {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Provider<String>>() {}).toInstance(Providers.of("A"));
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) Binding to Provider is not allowed.",
"at " + BinderTest.class.getName(), getDeclaringSourcePart(getClass()));
}
}
static class OuterCoreModule extends AbstractModule {
@Override protected void configure() {
install(new InnerCoreModule());
}
}
static class InnerCoreModule extends AbstractModule {
final Named red = Names.named("red");
@Override protected void configure() {
bind(AbstractModule.class).annotatedWith(red)
.toProvider(Providers.<AbstractModule>of(null));
bind(Binder.class).annotatedWith(red).toProvider(Providers.<Binder>of(null));
bind(Binding.class).annotatedWith(red).toProvider(Providers.<Binding>of(null));
bind(Injector.class).annotatedWith(red).toProvider(Providers.<Injector>of(null));
bind(Key.class).annotatedWith(red).toProvider(Providers.<Key>of(null));
bind(Module.class).annotatedWith(red).toProvider(Providers.<Module>of(null));
bind(Provider.class).annotatedWith(red).toProvider(Providers.<Provider>of(null));
bind(Scope.class).annotatedWith(red).toProvider(Providers.<Scope>of(null));
bind(Stage.class).annotatedWith(red).toProvider(Providers.<Stage>of(null));
bind(TypeLiteral.class).annotatedWith(red).toProvider(Providers.<TypeLiteral>of(null));
bind(new TypeLiteral<Key<String>>() {}).toProvider(Providers.<Key<String>>of(null));
}
}
public void testCannotBindToGuiceTypes() {
try {
Guice.createInjector(new OuterCoreModule());
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"Binding to core guice framework type is not allowed: AbstractModule.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Binder.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Binding.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Injector.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Key.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Module.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to Provider is not allowed.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Scope.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Stage.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: TypeLiteral.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
"Binding to core guice framework type is not allowed: Key.",
"at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
asModuleChain(OuterCoreModule.class, InnerCoreModule.class));
}
}
static class MissingParameter {
@Inject MissingParameter(NoInjectConstructor noInjectConstructor) {}
}
static class NoInjectConstructor {
private NoInjectConstructor() {}
}
@ProvidedBy(HasProvidedBy1Provider.class)
interface HasProvidedBy1 {}
static class HasProvidedBy1Provider implements Provider<HasProvidedBy1> {
public HasProvidedBy1 get() {
return new HasProvidedBy1() {};
}
}
@ImplementedBy(ImplementsHasImplementedBy1.class)
interface HasImplementedBy1 {}
static class ImplementsHasImplementedBy1 implements HasImplementedBy1 {}
@ProvidedBy(HasProvidedBy2Provider.class)
static class HasProvidedBy2 {}
static class HasProvidedBy2Provider implements Provider<HasProvidedBy2> {
public HasProvidedBy2 get() {
return new HasProvidedBy2() {};
}
}
@ImplementedBy(ExtendsHasImplementedBy2.class)
static class HasImplementedBy2 {}
static class ExtendsHasImplementedBy2 extends HasImplementedBy2 {}
static class JustAClass {}
@ImplementedBy(ImplementsHasImplementedByThatNeedsAnotherImplementedBy.class)
static interface HasImplementedByThatNeedsAnotherImplementedBy {
}
static class ImplementsHasImplementedByThatNeedsAnotherImplementedBy
implements HasImplementedByThatNeedsAnotherImplementedBy {
@Inject
ImplementsHasImplementedByThatNeedsAnotherImplementedBy(
HasImplementedBy1 h1n1) {}
}
@ImplementedBy(ImplementsHasImplementedByThatWantsExplicit.class)
static interface HasImplementedByThatWantsExplicit {
}
static class ImplementsHasImplementedByThatWantsExplicit
implements HasImplementedByThatWantsExplicit {
@Inject ImplementsHasImplementedByThatWantsExplicit(JustAnInterface jai) {}
}
static interface JustAnInterface {}
// public void testBindInterfaceWithoutImplementation() {
// Guice.createInjector(new AbstractModule() {
// protected void configure() {
// bind(Runnable.class);
// }
// }).getInstance(Runnable.class);
// }
enum Roshambo { ROCK, SCISSORS, PAPER }
public void testInjectRawProvider() {
try {
Guice.createInjector().getInstance(Provider.class);
fail();
} catch (ConfigurationException expected) {
Asserts.assertContains(expected.getMessage(),
"1) Cannot inject a Provider that has no type parameter",
"while locating " + Provider.class.getName());
}
}
}