/* * 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> } } }