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