1 package com.google.inject; 2 3 import static com.google.inject.Asserts.assertContains; 4 import static com.google.inject.Asserts.getDeclaringSourcePart; 5 6 import com.google.common.base.Optional; 7 import com.google.inject.multibindings.OptionalBinder; 8 import com.google.inject.name.Named; 9 import com.google.inject.name.Names; 10 import com.google.inject.util.Providers; 11 import java.lang.annotation.Documented; 12 import java.lang.annotation.ElementType; 13 import java.lang.annotation.Retention; 14 import java.lang.annotation.RetentionPolicy; 15 import java.lang.annotation.Target; 16 import junit.framework.TestCase; 17 18 /** @author jessewilson@google.com (Jesse Wilson) */ 19 public class NullableInjectionPointTest extends TestCase { 20 testInjectNullIntoNotNullableConstructor()21 public void testInjectNullIntoNotNullableConstructor() { 22 try { 23 createInjector().getInstance(FooConstructor.class); 24 fail("Injecting null should fail with an error"); 25 } catch (ProvisionException expected) { 26 assertContains( 27 expected.getMessage(), 28 "null returned by binding at " + getClass().getName(), 29 "the 1st parameter of " + FooConstructor.class.getName() + ".<init>(", 30 "is not @Nullable"); 31 } 32 } 33 testInjectNullIntoNotNullableMethod()34 public void testInjectNullIntoNotNullableMethod() { 35 try { 36 createInjector().getInstance(FooMethod.class); 37 fail("Injecting null should fail with an error"); 38 } catch (ProvisionException expected) { 39 assertContains( 40 expected.getMessage(), 41 "null returned by binding at " + getClass().getName(), 42 "the 1st parameter of " + FooMethod.class.getName() + ".setFoo(", 43 "is not @Nullable"); 44 } 45 } 46 testInjectNullIntoNotNullableField()47 public void testInjectNullIntoNotNullableField() { 48 try { 49 createInjector().getInstance(FooField.class); 50 fail("Injecting null should fail with an error"); 51 } catch (ProvisionException expected) { 52 assertContains( 53 expected.getMessage(), 54 "null returned by binding at " + getClass().getName(), 55 " but " + FooField.class.getName() + ".foo", 56 " is not @Nullable"); 57 } 58 } 59 60 /** Provider.getInstance() is allowed to return null via direct calls to getInstance(). */ testGetInstanceOfNull()61 public void testGetInstanceOfNull() { 62 assertNull(createInjector().getInstance(Foo.class)); 63 } 64 testInjectNullIntoNullableConstructor()65 public void testInjectNullIntoNullableConstructor() { 66 NullableFooConstructor nfc = createInjector().getInstance(NullableFooConstructor.class); 67 assertNull(nfc.foo); 68 } 69 testInjectNullIntoNullableMethod()70 public void testInjectNullIntoNullableMethod() { 71 NullableFooMethod nfm = createInjector().getInstance(NullableFooMethod.class); 72 assertNull(nfm.foo); 73 } 74 testInjectNullIntoNullableField()75 public void testInjectNullIntoNullableField() { 76 NullableFooField nff = createInjector().getInstance(NullableFooField.class); 77 assertNull(nff.foo); 78 } 79 testInjectNullIntoCustomNullableConstructor()80 public void testInjectNullIntoCustomNullableConstructor() { 81 CustomNullableFooConstructor nfc = 82 createInjector().getInstance(CustomNullableFooConstructor.class); 83 assertNull(nfc.foo); 84 } 85 testInjectNullIntoCustomNullableMethod()86 public void testInjectNullIntoCustomNullableMethod() { 87 CustomNullableFooMethod nfm = createInjector().getInstance(CustomNullableFooMethod.class); 88 assertNull(nfm.foo); 89 } 90 testInjectNullIntoCustomNullableField()91 public void testInjectNullIntoCustomNullableField() { 92 CustomNullableFooField nff = createInjector().getInstance(CustomNullableFooField.class); 93 assertNull(nff.foo); 94 } 95 createInjector()96 private Injector createInjector() { 97 return Guice.createInjector( 98 new AbstractModule() { 99 @Override 100 protected void configure() { 101 bind(Foo.class).toProvider(Providers.<Foo>of(null)); 102 } 103 }); 104 } 105 106 /** We haven't decided on what the desired behaviour of this test should be... */ 107 public void testBindNullToInstance() { 108 try { 109 Guice.createInjector( 110 new AbstractModule() { 111 @Override 112 protected void configure() { 113 bind(Foo.class).toInstance(null); 114 } 115 }); 116 fail(); 117 } catch (CreationException expected) { 118 assertContains( 119 expected.getMessage(), 120 "Binding to null instances is not allowed.", 121 "at " + getClass().getName(), 122 getDeclaringSourcePart(getClass())); 123 } 124 } 125 126 public void testBindNullToProvider() { 127 Injector injector = 128 Guice.createInjector( 129 new AbstractModule() { 130 @Override 131 protected void configure() { 132 bind(Foo.class).toProvider(Providers.<Foo>of(null)); 133 } 134 }); 135 assertNull(injector.getInstance(NullableFooField.class).foo); 136 assertNull(injector.getInstance(CustomNullableFooField.class).foo); 137 138 try { 139 injector.getInstance(FooField.class); 140 fail("Expected ProvisionException"); 141 } catch (ProvisionException expected) { 142 assertContains(expected.getMessage(), "null returned by binding at"); 143 } 144 } 145 146 public void testBindScopedNull() { 147 Injector injector = 148 Guice.createInjector( 149 new AbstractModule() { 150 @Override 151 protected void configure() { 152 bind(Foo.class).toProvider(Providers.<Foo>of(null)).in(Scopes.SINGLETON); 153 } 154 }); 155 assertNull(injector.getInstance(NullableFooField.class).foo); 156 assertNull(injector.getInstance(CustomNullableFooField.class).foo); 157 158 try { 159 injector.getInstance(FooField.class); 160 fail("Expected ProvisionException"); 161 } catch (ProvisionException expected) { 162 assertContains(expected.getMessage(), "null returned by binding at"); 163 } 164 } 165 166 public void testBindNullAsEagerSingleton() { 167 Injector injector = 168 Guice.createInjector( 169 new AbstractModule() { 170 @Override 171 protected void configure() { 172 bind(Foo.class).toProvider(Providers.<Foo>of(null)).asEagerSingleton(); 173 } 174 }); 175 assertNull(injector.getInstance(NullableFooField.class).foo); 176 assertNull(injector.getInstance(CustomNullableFooField.class).foo); 177 178 try { 179 injector.getInstance(FooField.class); 180 fail(); 181 } catch (ProvisionException expected) { 182 assertContains( 183 expected.getMessage(), 184 "null returned by binding " + "at com.google.inject.NullableInjectionPointTest"); 185 } 186 } 187 188 /** 189 * Tests for a regression where dependency objects were not updated properly and OptionalBinder 190 * was rejecting nulls from its dependencies. 191 */ 192 public void testBindNullAndLinkFromOptionalBinder() { 193 Injector injector = 194 Guice.createInjector( 195 new AbstractModule() { 196 @Override 197 protected void configure() { 198 bind(Foo.class).toProvider(Providers.<Foo>of(null)); 199 OptionalBinder.newOptionalBinder(binder(), Foo.class); 200 } 201 202 @Provides 203 @Named("throughProvidesMethod") 204 Foo provideFoo(Optional<Foo> foo) { 205 return foo.orNull(); 206 } 207 }); 208 assertNull(injector.getInstance(Key.get(Foo.class, Names.named("throughProvidesMethod")))); 209 } 210 211 static class Foo {} 212 213 static class FooConstructor { 214 @Inject 215 FooConstructor(Foo foo) {} 216 } 217 218 static class FooField { 219 @Inject Foo foo; 220 } 221 222 static class FooMethod { 223 @Inject 224 void setFoo(Foo foo) {} 225 } 226 227 static class NullableFooConstructor { 228 Foo foo; 229 230 @Inject 231 NullableFooConstructor(@Nullable Foo foo) { 232 this.foo = foo; 233 } 234 } 235 236 static class NullableFooField { 237 @Inject @Nullable Foo foo; 238 } 239 240 static class NullableFooMethod { 241 Foo foo; 242 243 @Inject 244 void setFoo(@Nullable Foo foo) { 245 this.foo = foo; 246 } 247 } 248 249 static class CustomNullableFooConstructor { 250 Foo foo; 251 252 @Inject 253 CustomNullableFooConstructor(@Namespace.Nullable Foo foo) { 254 this.foo = foo; 255 } 256 } 257 258 static class CustomNullableFooField { 259 @Inject @Namespace.Nullable Foo foo; 260 } 261 262 static class CustomNullableFooMethod { 263 Foo foo; 264 265 @Inject 266 void setFoo(@Namespace.Nullable Foo foo) { 267 this.foo = foo; 268 } 269 } 270 271 @Documented 272 @Retention(RetentionPolicy.RUNTIME) 273 @Target({ElementType.PARAMETER, ElementType.FIELD}) 274 @interface Nullable {} 275 276 static interface Namespace { 277 @Documented 278 @Retention(RetentionPolicy.RUNTIME) 279 @Target({ElementType.PARAMETER, ElementType.FIELD}) 280 @interface Nullable {} 281 } 282 } 283