blob: 2db12153cfaf9ac97cef1a53ed73daa554491b7c [file] [log] [blame]
/*
* Copyright (C) 2014 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.testing.fieldbinder;
import static com.google.inject.Asserts.assertContains;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.inject.BindingAnnotation;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.util.Providers;
import junit.framework.TestCase;
import java.lang.annotation.Retention;
import java.util.Arrays;
import java.util.List;
import javax.inject.Qualifier;
/** Unit tests for {@link BoundFieldModule}. */
public class BoundFieldModuleTest extends TestCase {
public void testBindingNothing() {
Object instance = new Object() {};
BoundFieldModule module = BoundFieldModule.of(instance);
Guice.createInjector(module);
// If we didn't throw an exception, we succeeded.
}
public void testBindingOnePrivate() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private Integer anInt = testValue;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testBindingOnePublic() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind public Integer anInt = testValue;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
private static class FieldBindableClass {
@Bind Integer anInt;
FieldBindableClass(Integer anInt) {
this.anInt = anInt;
}
}
private static class FieldBindableSubclass extends FieldBindableClass {
FieldBindableSubclass(Integer anInt) {
super(anInt);
}
}
public void testSuperTypeBinding() {
FieldBindableSubclass instance = new FieldBindableSubclass(1024);
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(instance.anInt, injector.getInstance(Integer.class));
}
public void testBindingTwo() {
final Integer testValue = 1024;
final String testString = "Hello World!";
Object instance = new Object() {
@Bind private Integer anInt = testValue;
@Bind private String aString = testString;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
assertEquals(testString, injector.getInstance(String.class));
}
public void testBindingSuperType() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = Number.class) private Integer anInt = testValue;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Number.class));
}
public void testBindingSuperTypeAccessSubType() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = Number.class) private Integer anInt = testValue;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
try {
injector.getInstance(Integer.class);
} catch (ConfigurationException e) {
assertContains(
e.getMessage(),
"Could not find a suitable constructor in java.lang.Integer");
}
}
public void testBindingIncorrectTypeProviderFails() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = String.class) private Provider<Integer> anIntProvider = new Provider<Integer>() {
@Override public Integer get() {
return testValue;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Requested binding type \"java.lang.String\" is not "
+ "assignable from field binding type \"java.lang.Integer\"");
}
}
@BindingAnnotation
@Retention(RUNTIME)
private static @interface SomeBindingAnnotation {}
public void testBindingWithBindingAnnotation() {
final Integer testValue1 = 1024, testValue2 = 2048;
Object instance = new Object() {
@Bind private Integer anInt = testValue1;
@Bind
@SomeBindingAnnotation
private Integer anotherInt = testValue2;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue1, injector.getInstance(Integer.class));
assertEquals(
testValue2,
injector.getInstance(Key.get(Integer.class, SomeBindingAnnotation.class)));
}
@Qualifier
@Retention(RUNTIME)
private static @interface SomeQualifier {}
public void testBindingWithQualifier() {
final Integer testValue1 = 1024, testValue2 = 2048;
Object instance = new Object() {
@Bind private Integer anInt = testValue1;
@Bind
@SomeQualifier
private Integer anotherInt = testValue2;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue1, injector.getInstance(Integer.class));
assertEquals(
testValue2,
injector.getInstance(Key.get(Integer.class, SomeQualifier.class)));
}
public void testCanReuseBindingAnnotationsWithDifferentValues() {
final Integer testValue1 = 1024, testValue2 = 2048;
final String name1 = "foo", name2 = "bar";
Object instance = new Object() {
@Bind
@Named(name1)
private Integer anInt = testValue1;
@Bind
@Named(name2)
private Integer anotherInt = testValue2;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(
testValue1,
injector.getInstance(Key.get(Integer.class, Names.named(name1))));
assertEquals(
testValue2,
injector.getInstance(Key.get(Integer.class, Names.named(name2))));
}
public void testBindingWithValuedBindingAnnotation() {
final Integer testValue1 = 1024, testValue2 = 2048;
final String name = "foo";
Object instance = new Object() {
@Bind private Integer anInt = testValue1;
@Bind
@Named(name)
private Integer anotherInt = testValue2;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue1, injector.getInstance(Integer.class));
assertEquals(
testValue2,
injector.getInstance(Key.get(Integer.class, Names.named(name))));
}
public void testBindingWithGenerics() {
final List<Integer> testIntList = Arrays.asList(new Integer[] {1, 2, 3});
final List<Boolean> testBoolList = Arrays.asList(new Boolean[] {true, true, false});
Object instance = new Object() {
@Bind private List<Integer> anIntList = testIntList;
@Bind private List<Boolean> aBoolList = testBoolList;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testIntList, injector.getInstance(new Key<List<Integer>>() {}));
assertEquals(testBoolList, injector.getInstance(new Key<List<Boolean>>() {}));
}
public void testBoundValueDoesntChange() {
Integer testValue = 1024;
FieldBindableClass instance = new FieldBindableClass(testValue);
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
instance.anInt++;
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testIncompatibleBindingType() {
final Integer testInt = 1024;
Object instance = new Object() {
@Bind(to = String.class) private Integer anInt = testInt;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(),
"Requested binding type \"java.lang.String\" is not assignable from field binding type "
+ "\"java.lang.Integer\"");
}
}
public void testFailureOnMultipleBindingAnnotations() {
final Integer testInt = 1024;
Object instance = new Object() {
@Bind
@Named("a")
@SomeBindingAnnotation
private Integer anInt = testInt;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
} catch (CreationException e) {
assertContains(e.getMessage(), "More than one annotation is specified for this binding.");
}
}
public void testBindingSuperTypeAndBindingAnnotation() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = Number.class)
@Named("foo")
private Integer anInt = testValue;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Key.get(Number.class, Names.named("foo"))));
}
public void testBindingProvider() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private Provider<Integer> anInt = new Provider<Integer>() {
@Override public Integer get() {
return testValue;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testBindingNullField() {
Object instance = new Object() {
@Bind private Integer anInt = null;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(),
"Binding to null values is not allowed. "
+ "Use Providers.of(null) if this is your intended behavior.");
}
}
public void testBindingNullProvider() {
Object instance = new Object() {
@Bind private Provider<Integer> anIntProvider = null;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(),
"Binding to null values is not allowed. "
+ "Use Providers.of(null) if this is your intended behavior.");
}
}
private static class IntegerProvider implements Provider<Integer> {
private final Integer value;
IntegerProvider(Integer value) {
this.value = value;
}
@Override public Integer get() {
return value;
}
}
public void testProviderSubclassesBindToTheProviderItself() {
final IntegerProvider integerProvider = new IntegerProvider(1024);
Object instance = new Object() {
@Bind private IntegerProvider anIntProvider = integerProvider;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(integerProvider, injector.getInstance(IntegerProvider.class));
}
public void testProviderSubclassesDoNotBindParameterizedType() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private IntegerProvider anIntProvider = new IntegerProvider(testValue);
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
try {
injector.getInstance(Integer.class);
fail();
} catch (ConfigurationException e) {
assertContains(e.getMessage(), "Could not find a suitable constructor in java.lang.Integer.");
}
}
private static class ParameterizedObject<T> {
ParameterizedObject(T instance) {
this.instance = instance;
}
@Bind private T instance;
}
public void testBindParameterizedTypeFails() {
ParameterizedObject<Integer> instance = new ParameterizedObject<Integer>(0);
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(), "T cannot be used as a key; It is not fully specified.");
}
}
public void testBindSubclassOfParameterizedTypeSucceeds() {
final Integer testValue = 1024;
ParameterizedObject<Integer> instance = new ParameterizedObject<Integer>(testValue) {};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testBindArray() {
final Integer[] testArray = new Integer[] { 1024, 2048 };
Object instance = new Object() {
@Bind private Integer[] anIntArray = testArray;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testArray, injector.getInstance(Integer[].class));
}
public void testRawProviderCannotBeBound() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private Provider anIntProvider = new Provider() {
@Override public Object get() {
return testValue;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(),
"Non parameterized Provider fields must have an "
+ "explicit binding class via @Bind(to = Foo.class)");
}
}
public void testExplicitlyBoundRawProviderCanBeBound() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = Integer.class) private Provider anIntProvider = new Provider() {
@Override public Object get() {
return testValue;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testRawProviderCanBindToIncorrectType() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind(to = String.class) private Provider anIntProvider = new Provider() {
@Override public Object get() {
return testValue;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(String.class));
}
public void testMultipleErrorsAreAggregated() {
Object instance = new Object() {
@Bind private Provider aProvider;
@Bind(to = String.class) private Integer anInt;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
} catch (CreationException e) {
assertEquals(2, e.getErrorMessages().size());
}
}
public void testBindingProviderWithProviderSubclassValue() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private Provider<Integer> anIntProvider = new IntegerProvider(testValue);
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Integer.class));
}
public void testBoundFieldsCannotBeInjected() {
Object instance = new Object() {
@Bind
@Inject
Integer anInt = 0;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Fields annotated with both @Bind and @Inject are illegal.");
}
}
public void testIncrementingProvider() {
final Integer testBaseValue = 1024;
Object instance = new Object() {
@Bind private Provider<Integer> anIntProvider = new Provider<Integer>() {
private int value = testBaseValue;
@Override public Integer get() {
return value++;
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testBaseValue, injector.getInstance(Integer.class));
assertEquals((Integer) (testBaseValue + 1), injector.getInstance(Integer.class));
assertEquals((Integer) (testBaseValue + 2), injector.getInstance(Integer.class));
}
public void testProviderDoesNotProvideDuringInjectorConstruction() {
Object instance = new Object() {
@Bind private Provider<Integer> myIntProvider = new Provider<Integer>() {
@Override public Integer get() {
throw new UnsupportedOperationException();
}
};
};
BoundFieldModule module = BoundFieldModule.of(instance);
Guice.createInjector(module);
// If we don't throw an exception, we succeeded.
}
private static class InvalidBindableClass {
@Bind(to = String.class) Integer anInt;
}
public void testIncompatibleBindingTypeStackTraceHasUserFrame() {
Object instance = new InvalidBindableClass();
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(e.getMessage(), "at " + InvalidBindableClass.class.getName() + " field anInt");
}
}
private static class InjectedNumberProvider implements Provider<Number> {
@Inject Integer anInt;
@Override public Number get() {
return anInt;
}
}
public void testBoundProvidersAreInjected() {
final Integer testValue = 1024;
Object instance = new Object() {
@Bind private Integer anInt = testValue;
@Bind private Provider<Number> aNumberProvider = new InjectedNumberProvider();
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue, injector.getInstance(Number.class));
}
public void testBoundInstancesAreInjected() {
final Integer testValue = 1024;
final InjectedNumberProvider testNumberProvider = new InjectedNumberProvider();
Object instance = new Object() {
@Bind private Integer anInt = testValue;
@Bind private InjectedNumberProvider aNumberProvider = testNumberProvider;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Guice.createInjector(module);
assertEquals(testValue, testNumberProvider.anInt);
}
private static class InvalidBindableSubclass extends InvalidBindableClass {}
public void testClassIsPrintedInErrorsWhenCauseIsSuperclass() {
Object instance = new InvalidBindableSubclass();
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Requested binding type \"java.lang.String\" is not assignable from field binding type "
+ "\"java.lang.Integer\"");
}
}
private static class FieldBindableSubclass2 extends FieldBindableClass {
@Bind Number aNumber;
FieldBindableSubclass2(Integer anInt, Number aNumber) {
super(anInt);
this.aNumber = aNumber;
}
}
public void testFieldsAreBoundFromFullClassHierarchy() {
final Integer testValue1 = 1024, testValue2 = 2048;
FieldBindableSubclass2 instance = new FieldBindableSubclass2(testValue1, testValue2);
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertEquals(testValue1, injector.getInstance(Integer.class));
assertEquals(testValue2, injector.getInstance(Number.class));
}
static final class LazyClass {
@Bind(lazy = true) Integer foo = 1;
}
public void testFieldBound_lazy() {
LazyClass asProvider = new LazyClass();
Injector injector = Guice.createInjector(BoundFieldModule.of(asProvider));
assertEquals(1, injector.getInstance(Integer.class).intValue());
asProvider.foo++;
assertEquals(2, injector.getInstance(Integer.class).intValue());
}
public void testFieldBound_lazy_rejectNull() {
LazyClass asProvider = new LazyClass();
Injector injector = Guice.createInjector(BoundFieldModule.of(asProvider));
assertEquals(1, injector.getInstance(Integer.class).intValue());
asProvider.foo = null;
try {
injector.getInstance(Integer.class);
fail();
} catch (ProvisionException e) {
assertContains(e.getMessage(),
"Binding to null values is not allowed. "
+ "Use Providers.of(null) if this is your intended behavior.");
}
}
static final class LazyProviderClass {
@Bind(lazy = true) Provider<Integer> foo = Providers.of(null);
}
public void testFieldBoundAsProvider_rejectProvider() {
LazyProviderClass asProvider = new LazyProviderClass();
try {
Guice.createInjector(BoundFieldModule.of(asProvider));
fail();
} catch (CreationException e) {
assertContains(e.getMessage(), "'lazy' is incompatible with Provider valued fields");
}
}
}