blob: 9e00d5b50b53de08c6f07b43b9762213e0c17ddb [file] [log] [blame]
/**
* 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.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.util.ImmutableList;
import com.google.inject.internal.util.Lists;
import com.google.inject.name.Named;
import static com.google.inject.name.Names.named;
import com.google.inject.util.Providers;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* @author jessewilson@google.com (Jesse Wilson)
*/
public class BinderTestSuite {
public static Test suite() {
TestSuite suite = new TestSuite();
new Builder()
.name("bind A")
.module(new AbstractModule() {
protected void configure() {
bind(A.class);
}
})
.creationException("No implementation for %s was bound", A.class.getName())
.addToSuite(suite);
new Builder()
.name("bind PlainA named apple")
.module(new AbstractModule() {
protected void configure() {
bind(PlainA.class).annotatedWith(named("apple"));
}
})
.creationException("No implementation for %s annotated with %s was bound",
PlainA.class.getName(), named("apple"))
.addToSuite(suite);
new Builder()
.name("bind A to new PlainA(1)")
.module(new AbstractModule() {
protected void configure() {
bind(A.class).toInstance(new PlainA(1));
}
})
.creationTime(CreationTime.NONE)
.expectedValues(new PlainA(1), new PlainA(1), new PlainA(1))
.addToSuite(suite);
new Builder()
.name("no binding, AWithProvidedBy")
.key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class)
.addToSuite(suite);
new Builder()
.name("no binding, AWithImplementedBy")
.key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class)
.addToSuite(suite);
new Builder()
.name("no binding, ScopedA")
.key(Key.get(ScopedA.class), InjectsScopedA.class)
.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202))
.addToSuite(suite);
new Builder()
.name("no binding, AWithProvidedBy named apple")
.key(Key.get(AWithProvidedBy.class, named("apple")),
InjectsAWithProvidedByNamedApple.class)
.configurationException("No implementation for %s annotated with %s was bound",
AWithProvidedBy.class.getName(), named("apple"))
.addToSuite(suite);
new Builder()
.name("no binding, AWithImplementedBy named apple")
.key(Key.get(AWithImplementedBy.class, named("apple")),
InjectsAWithImplementedByNamedApple.class)
.configurationException("No implementation for %s annotated with %s was bound",
AWithImplementedBy.class.getName(), named("apple"))
.addToSuite(suite);
new Builder()
.name("no binding, ScopedA named apple")
.key(Key.get(ScopedA.class, named("apple")), InjectsScopedANamedApple.class)
.configurationException("No implementation for %s annotated with %s was bound",
ScopedA.class.getName(), named("apple"))
.addToSuite(suite);
for (final Scoper scoper : Scoper.values()) {
new Builder()
.name("bind PlainA")
.key(Key.get(PlainA.class), InjectsPlainA.class)
.module(new AbstractModule() {
protected void configure() {
AnnotatedBindingBuilder<PlainA> abb = bind(PlainA.class);
scoper.configure(abb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind A to PlainA")
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(A.class).to(PlainA.class);
scoper.configure(sbb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind A to PlainAProvider.class")
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(A.class).toProvider(PlainAProvider.class);
scoper.configure(sbb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind A to new PlainAProvider()")
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(A.class).toProvider(new PlainAProvider());
scoper.configure(sbb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind AWithProvidedBy")
.key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class)
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(AWithProvidedBy.class);
scoper.configure(sbb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind AWithImplementedBy")
.key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class)
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(AWithImplementedBy.class);
scoper.configure(sbb);
}
})
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind ScopedA")
.key(Key.get(ScopedA.class), InjectsScopedA.class)
.module(new AbstractModule() {
protected void configure() {
ScopedBindingBuilder sbb = bind(ScopedA.class);
scoper.configure(sbb);
}
})
.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202))
.scoper(scoper)
.addToSuite(suite);
new Builder()
.name("bind AWithProvidedBy named apple")
.module(new AbstractModule() {
protected void configure() {
scoper.configure(bind(AWithProvidedBy.class).annotatedWith(named("apple")));
}
})
.creationException("No implementation for %s annotated with %s was bound",
AWithProvidedBy.class.getName(), named("apple"))
.addToSuite(suite);
new Builder()
.name("bind AWithImplementedBy named apple")
.module(new AbstractModule() {
protected void configure() {
scoper.configure(bind(AWithImplementedBy.class).annotatedWith(named("apple")));
}
})
.creationException("No implementation for %s annotated with %s was bound",
AWithImplementedBy.class.getName(), named("apple"))
.addToSuite(suite);
new Builder()
.name("bind ScopedA named apple")
.module(new AbstractModule() {
protected void configure() {
scoper.configure(bind(ScopedA.class).annotatedWith(named("apple")));
}
})
.creationException("No implementation for %s annotated with %s was bound",
ScopedA.class.getName(), named("apple"))
.addToSuite(suite);
}
return suite;
}
enum Scoper {
UNSCOPED {
void configure(ScopedBindingBuilder sbb) {}
void apply(Builder builder) {}
},
EAGER_SINGLETON {
void configure(ScopedBindingBuilder sbb) {
sbb.asEagerSingleton();
}
void apply(Builder builder) {
builder.expectedValues(new PlainA(101), new PlainA(101), new PlainA(101));
builder.creationTime(CreationTime.EAGER);
}
},
SCOPES_SINGLETON {
void configure(ScopedBindingBuilder sbb) {
sbb.in(Scopes.SINGLETON);
}
void apply(Builder builder) {
builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201));
}
},
SINGLETON_DOT_CLASS {
void configure(ScopedBindingBuilder sbb) {
sbb.in(Singleton.class);
}
void apply(Builder builder) {
builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201));
}
},
TWO_AT_A_TIME_SCOPED_DOT_CLASS {
void configure(ScopedBindingBuilder sbb) {
sbb.in(TwoAtATimeScoped.class);
}
void apply(Builder builder) {
builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
}
},
TWO_AT_A_TIME_SCOPE {
void configure(ScopedBindingBuilder sbb) {
sbb.in(new TwoAtATimeScope());
}
void apply(Builder builder) {
builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
}
};
abstract void configure(ScopedBindingBuilder sbb);
abstract void apply(Builder builder);
}
/** When Guice creates a value, directly or via a provider */
enum CreationTime {
NONE, EAGER, LAZY
}
public static class Builder {
private String name = "test";
private Key<?> key = Key.get(A.class);
private Class<? extends Injectable> injectsKey = InjectsA.class;
private List<Module> modules = Lists.<Module>newArrayList(new AbstractModule() {
protected void configure() {
bindScope(TwoAtATimeScoped.class, new TwoAtATimeScope());
}
});
private List<Object> expectedValues = Lists.<Object>newArrayList(
new PlainA(201), new PlainA(202), new PlainA(203));
private CreationTime creationTime = CreationTime.LAZY;
private String creationException;
private String configurationException;
public Builder module(Module module) {
this.modules.add(module);
return this;
}
public Builder creationTime(CreationTime creationTime) {
this.creationTime = creationTime;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder key(Key<?> key, Class<? extends Injectable> injectsKey) {
this.key = key;
this.injectsKey = injectsKey;
return this;
}
private Builder creationException(String message, Object... args) {
this.creationException = String.format(message, args);
return this;
}
private Builder configurationException(String message, Object... args) {
configurationException = String.format(message, args);
return this;
}
private Builder scoper(Scoper scoper) {
name(name + " in " + scoper.toString());
scoper.apply(this);
return this;
}
private <T> Builder expectedValues(T... values) {
this.expectedValues.clear();
this.expectedValues.addAll(Arrays.asList(values));
return this;
}
public void addToSuite(TestSuite suite) {
if (creationException != null) {
suite.addTest(new CreationExceptionTest(this));
} else if (configurationException != null) {
suite.addTest(new ConfigurationExceptionTest(this));
} else {
suite.addTest(new SuccessTest(this));
if (creationTime != CreationTime.NONE) {
suite.addTest(new UserExceptionsTest(this));
}
}
}
}
public static class SuccessTest extends TestCase {
final String name;
final Key<?> key;
final Class<? extends Injectable> injectsKey;
final ImmutableList<Module> modules;
final ImmutableList<Object> expectedValues;
public SuccessTest(Builder builder) {
super("test");
name = builder.name;
key = builder.key;
injectsKey = builder.injectsKey;
modules = ImmutableList.copyOf(builder.modules);
expectedValues = ImmutableList.copyOf(builder.expectedValues);
}
public String getName() {
return name;
}
Injector newInjector() {
nextId.set(101);
return Guice.createInjector(modules);
}
public void test() throws IllegalAccessException, InstantiationException {
Injector injector = newInjector();
nextId.set(201);
for (Object value : expectedValues) {
assertEquals(value, injector.getInstance(key));
}
Provider<?> provider = newInjector().getProvider(key);
nextId.set(201);
for (Object value : expectedValues) {
assertEquals(value, provider.get());
}
Provider<?> bindingProvider = newInjector().getBinding(key).getProvider();
nextId.set(201);
for (Object value : expectedValues) {
assertEquals(value, bindingProvider.get());
}
injector = newInjector();
nextId.set(201);
for (Object value : expectedValues) {
Injectable instance = injector.getInstance(injectsKey);
assertEquals(value, instance.value);
}
injector = newInjector();
nextId.set(201);
for (Object value : expectedValues) {
Injectable injectable = injectsKey.newInstance();
injector.injectMembers(injectable);
assertEquals(value, injectable.value);
}
Injector injector1 = newInjector();
nextId.set(201);
Injectable hasProvider = injector1.getInstance(injectsKey);
hasProvider.provider.get();
nextId.set(201);
for (Object value : expectedValues) {
assertEquals(value, hasProvider.provider.get());
}
}
}
public static class CreationExceptionTest extends TestCase {
final String name;
final Key<?> key;
final ImmutableList<Module> modules;
final String creationException;
public CreationExceptionTest(Builder builder) {
super("test");
name = builder.name;
key = builder.key;
modules = ImmutableList.copyOf(builder.modules);
creationException = builder.creationException;
}
public String getName() {
return "creation errors:" + name;
}
public void test() {
try {
Guice.createInjector(modules);
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(), creationException);
}
}
}
public static class ConfigurationExceptionTest extends TestCase {
final String name;
final Key<?> key;
final Class<? extends Injectable> injectsKey;
final ImmutableList<Module> modules;
final String configurationException;
public ConfigurationExceptionTest(Builder builder) {
super("test");
name = builder.name;
key = builder.key;
injectsKey = builder.injectsKey;
modules = ImmutableList.copyOf(builder.modules);
configurationException = builder.configurationException;
}
public String getName() {
return "provision errors:" + name;
}
Injector newInjector() {
return Guice.createInjector(modules);
}
public void test() throws IllegalAccessException, InstantiationException {
try {
newInjector().getProvider(key);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(), configurationException);
}
try {
newInjector().getBinding(key).getProvider();
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(), configurationException);
}
try {
newInjector().getInstance(key);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(), configurationException);
}
try {
newInjector().getInstance(injectsKey);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(),
configurationException, injectsKey.getName() + ".inject",
configurationException, injectsKey.getName() + ".inject",
"2 errors");
}
try {
Injectable injectable = injectsKey.newInstance();
newInjector().injectMembers(injectable);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(),
configurationException, injectsKey.getName() + ".inject",
configurationException, injectsKey.getName() + ".inject",
"2 errors");
}
}
}
public static class UserExceptionsTest extends TestCase {
final String name;
final Key<?> key;
final Class<? extends Injectable> injectsKey;
final ImmutableList<Module> modules;
final ImmutableList<Object> expectedValues;
final CreationTime creationTime;
public UserExceptionsTest(Builder builder) {
super("test");
name = builder.name;
key = builder.key;
injectsKey = builder.injectsKey;
modules = ImmutableList.copyOf(builder.modules);
expectedValues = ImmutableList.copyOf(builder.expectedValues);
creationTime = builder.creationTime;
}
public String getName() {
return "provision errors:" + name;
}
Injector newInjector() {
return Guice.createInjector(modules);
}
public void test() throws IllegalAccessException, InstantiationException {
nextId.set(-1);
try {
newInjector();
assertEquals(CreationTime.LAZY, creationTime);
} catch (CreationException expected) {
assertEquals(CreationTime.EAGER, creationTime);
assertContains(expected.getMessage(), "Illegal value: -1");
return;
}
Provider<?> provider = newInjector().getProvider(key);
Provider<?> bindingProvider = newInjector().getBinding(key).getProvider();
nextId.set(-1);
try {
newInjector().getInstance(key);
fail();
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1");
}
nextId.set(-1);
try {
provider.get();
fail();
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1");
}
nextId.set(-1);
try {
bindingProvider.get();
fail();
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1");
}
try {
nextId.set(-1);
newInjector().getInstance(injectsKey);
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1",
"for parameter 0 at " + injectsKey.getName() + ".inject");
}
nextId.set(201);
Injectable injectable = injectsKey.newInstance();
try {
nextId.set(-1);
newInjector().injectMembers(injectable);
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1",
"for parameter 0 at " + injectsKey.getName() + ".inject");
}
nextId.set(201);
Injectable hasProvider = newInjector().getInstance(injectsKey);
hasProvider.provider.get();
try {
nextId.set(-1);
hasProvider.provider.get();
} catch (ProvisionException expected) {
assertContains(expected.getMessage(), "Illegal value: -1");
}
}
}
/** negative to throw, 101... for eager singletons, 201... for everything else */
static final AtomicInteger nextId = new AtomicInteger();
@ProvidedBy(PlainAProvider.class)
interface AWithProvidedBy {}
static class InjectsAWithProvidedBy extends Injectable {
@Inject public void inject(AWithProvidedBy aWithProvidedBy,
Provider<AWithProvidedBy> aWithProvidedByProvider) {
this.value = aWithProvidedBy;
this.provider = aWithProvidedByProvider;
}
}
static class InjectsAWithProvidedByNamedApple extends Injectable {
@Inject public void inject(@Named("apple") AWithProvidedBy aWithProvidedBy,
@Named("apple") Provider<AWithProvidedBy> aWithProvidedByProvider) {
this.value = aWithProvidedBy;
this.provider = aWithProvidedByProvider;
}
}
@ImplementedBy(PlainA.class)
interface AWithImplementedBy {}
static class InjectsAWithImplementedBy extends Injectable {
@Inject public void inject(AWithImplementedBy aWithImplementedBy,
Provider<AWithImplementedBy> aWithImplementedByProvider) {
this.value = aWithImplementedBy;
this.provider = aWithImplementedByProvider;
}
}
static class InjectsAWithImplementedByNamedApple extends Injectable {
@Inject public void inject(@Named("apple") AWithImplementedBy aWithImplementedBy,
@Named("apple") Provider<AWithImplementedBy> aWithImplementedByProvider) {
this.value = aWithImplementedBy;
this.provider = aWithImplementedByProvider;
}
}
interface A extends AWithProvidedBy, AWithImplementedBy {}
static class InjectsA extends Injectable {
@Inject public void inject(A a, Provider<A> aProvider) {
this.value = a;
this.provider = aProvider;
}
}
static class PlainA implements A {
final int value;
PlainA() {
value = nextId.getAndIncrement();
if (value < 0) {
throw new RuntimeException("Illegal value: " + value);
}
}
PlainA(int value) {
this.value = value;
}
public boolean equals(Object obj) {
return obj instanceof PlainA
&& value == ((PlainA) obj).value;
}
public int hashCode() {
return value;
}
public String toString() {
return "PlainA#" + value;
}
}
static class PlainAProvider implements Provider<A> {
public A get() {
return new PlainA();
}
}
static class InjectsPlainA extends Injectable {
@Inject public void inject(PlainA plainA, Provider<PlainA> plainAProvider) {
this.value = plainA;
this.provider = plainAProvider;
}
}
/** This scope hands out each value exactly twice */
static class TwoAtATimeScope implements Scope {
public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped) {
return new Provider<T>() {
T instance;
public T get() {
if (instance == null) {
instance = unscoped.get();
return instance;
} else {
T result = instance;
instance = null;
return result;
}
}
};
}
}
@Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation
public @interface TwoAtATimeScoped {}
@TwoAtATimeScoped
static class ScopedA extends PlainA {}
static class InjectsScopedA extends Injectable {
@Inject public void inject(ScopedA scopedA, Provider<ScopedA> scopedAProvider) {
this.value = scopedA;
this.provider = scopedAProvider;
}
}
static class InjectsScopedANamedApple extends Injectable {
@Inject public void inject(@Named("apple") ScopedA scopedA,
@Named("apple") Provider<ScopedA> scopedAProvider) {
this.value = scopedA;
this.provider = scopedAProvider;
}
}
static class Injectable {
Object value = new Object();
Provider<?> provider = Providers.of(new Object());
}
}