/*
* 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 java.lang.annotation.Retention;
import java.util.Arrays;
import java.util.List;
import javax.inject.Qualifier;
import junit.framework.TestCase;
/** 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);
fail();
} 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);
fail();
} 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 testBindingJavaxProvider() {
final Integer testValue = 1024;
Object instance =
new Object() {
@Bind
private javax.inject.Provider<Integer> anInt =
new javax.inject.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 testBindingNonNullableNullField() {
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 only allowed for fields that are annotated @Nullable.");
}
}
@Retention(RUNTIME)
private @interface Nullable {}
public void testBindingNullableNullField() {
Object instance =
new Object() {
@Bind @Nullable private Integer anInt = null;
};
Injector injector = Guice.createInjector(BoundFieldModule.of(instance));
assertNull(injector.getInstance(Integer.class));
}
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 is not allowed. Use Providers.of(null) if this is your intended "
+ "behavior.");
}
}
public void testBindingNullableNullProvider() {
Object instance =
new Object() {
@Bind @Nullable private Provider<Integer> anIntProvider = null;
};
BoundFieldModule module = BoundFieldModule.of(instance);
try {
Guice.createInjector(module);
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Binding to null 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.");
}
}
public void testNullableProviderSubclassesAllowNull() {
Object instance =
new Object() {
@Bind @Nullable private IntegerProvider anIntProvider = null;
};
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertNull(injector.getInstance(IntegerProvider.class));
}
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);
fail();
} 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);
fail();
} 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);
fail();
} 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 testNonNullableFieldBound_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 only allowed for fields that are annotated @Nullable.");
}
}
static final class LazyClassNullable {
@Bind(lazy = true)
@Nullable
Integer foo = 1;
}
public void testNullableFieldBound_lazy_allowNull() {
LazyClassNullable asProvider = new LazyClassNullable();
Injector injector = Guice.createInjector(BoundFieldModule.of(asProvider));
assertEquals(1, injector.getInstance(Integer.class).intValue());
asProvider.foo = null;
assertNull(injector.getInstance(Integer.class));
}
static final class LazyProviderClass {
@Bind(lazy = true)
Provider<Integer> foo = Providers.of(null);
}
public void testFieldBoundAsProvider_lazy() {
LazyProviderClass asProvider = new LazyProviderClass();
Provider<Integer> provider =
Guice.createInjector(BoundFieldModule.of(asProvider)).getProvider(Integer.class);
assertNull(provider.get());
asProvider.foo = Providers.of(1);
assertEquals(1, provider.get().intValue());
asProvider.foo =
new Provider<Integer>() {
@Override
public Integer get() {
throw new RuntimeException("boom");
}
};
try {
provider.get();
fail();
} catch (ProvisionException e) {
assertContains(e.getMessage(), "boom");
}
}
private static final class LazyNonTransparentProvider {
@Bind(lazy = true)
@Nullable
private IntegerProvider anIntProvider = null;
}
public void testFieldBoundAsNonTransparentProvider_lazy() {
LazyNonTransparentProvider instance = new LazyNonTransparentProvider();
BoundFieldModule module = BoundFieldModule.of(instance);
Injector injector = Guice.createInjector(module);
assertNull(injector.getInstance(IntegerProvider.class));
instance.anIntProvider = new IntegerProvider(3);
assertEquals(3, injector.getInstance(IntegerProvider.class).get().intValue());
try {
injector.getInstance(Integer.class);
fail();
} catch (ConfigurationException expected) {
// expected because we don't interpret IntegerProvider as a Provider<Integer>
}
}
}