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