xref: /aosp_15_r20/external/dagger2/javatests/dagger/functional/nullables/NullabilityTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1 /*
2  * Copyright (C) 2015 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.functional.nullables;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.fail;
21 
22 import dagger.Component;
23 import dagger.Module;
24 import dagger.Provides;
25 import dagger.Reusable;
26 import javax.inject.Inject;
27 import javax.inject.Provider;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.junit.runners.JUnit4;
31 
32 @RunWith(JUnit4.class)
33 public class NullabilityTest {
34   @interface Nullable {}
35 
36   @Component(dependencies = NullComponent.class)
37   interface NullComponentWithDependency {
string()38     @Nullable String string();
number()39     Number number();
stringProvider()40     Provider<String> stringProvider();
numberProvider()41     Provider<Number> numberProvider();
42   }
43 
44   @Component(modules = NullModule.class)
45   interface NullComponent {
string()46     @Nullable String string();
integer()47     @Nullable Integer integer();
nullFoo()48     NullFoo nullFoo();
number()49     Number number();
stringProvider()50     Provider<String> stringProvider();
numberProvider()51     Provider<Number> numberProvider();
52   }
53 
54   @Module
55   static class NullModule {
56     Number numberValue = null;
57     Integer integerCallCount = 0;
58 
59     @Nullable
60     @Provides
provideNullableString()61     String provideNullableString() {
62       return null;
63     }
64 
65     @Provides
provideNumber()66     Number provideNumber() {
67       return numberValue;
68     }
69 
70     @Nullable
71     @Provides
72     @Reusable
provideNullReusableInteger()73     Integer provideNullReusableInteger() {
74       integerCallCount++;
75       return null;
76     }
77   }
78 
79   @SuppressWarnings("BadInject") // This is just for testing purposes.
80   static class NullFoo {
81     final String string;
82     final Number number;
83     final Provider<String> stringProvider;
84     final Provider<Number> numberProvider;
85 
86     @Inject
NullFoo( @ullable String string, Number number, Provider<String> stringProvider, Provider<Number> numberProvider)87     NullFoo(
88         @Nullable String string,
89         Number number,
90         Provider<String> stringProvider,
91         Provider<Number> numberProvider) {
92       this.string = string;
93       this.number = number;
94       this.stringProvider = stringProvider;
95       this.numberProvider = numberProvider;
96     }
97 
98     String methodInjectedString;
99     Number methodInjectedNumber;
100     Provider<String> methodInjectedStringProvider;
101     Provider<Number> methodInjectedNumberProvider;
inject( @ullable String string, Number number, Provider<String> stringProvider, Provider<Number> numberProvider)102     @Inject void inject(
103         @Nullable String string,
104         Number number,
105         Provider<String> stringProvider,
106         Provider<Number> numberProvider) {
107       this.methodInjectedString = string;
108       this.methodInjectedNumber = number;
109       this.methodInjectedStringProvider = stringProvider;
110       this.methodInjectedNumberProvider = numberProvider;
111     }
112 
113     @Nullable @Inject String fieldInjectedString;
114     @Inject Number fieldInjectedNumber;
115     @Inject Provider<String> fieldInjectedStringProvider;
116     @Inject Provider<Number> fieldInjectedNumberProvider;
117   }
118 
119   @Test
testNullability_provides()120   public void testNullability_provides() {
121     NullModule module = new NullModule();
122     NullComponent component =
123         DaggerNullabilityTest_NullComponent.builder().nullModule(module).build();
124 
125     // Can't construct NullFoo because it depends on Number, and Number was null.
126     try {
127       component.nullFoo();
128       fail();
129     } catch (NullPointerException npe) {
130       assertThat(npe)
131           .hasMessageThat()
132           .isEqualTo("Cannot return null from a non-@Nullable @Provides method");
133     }
134 
135     // set number to non-null so we can create
136     module.numberValue = 1;
137     NullFoo nullFoo = component.nullFoo();
138 
139     // Then set it back to null so we can test its providers.
140     module.numberValue = null;
141     validate(true, nullFoo.string, nullFoo.stringProvider, nullFoo.numberProvider);
142     validate(true, nullFoo.methodInjectedString, nullFoo.methodInjectedStringProvider,
143         nullFoo.methodInjectedNumberProvider);
144     validate(true, nullFoo.fieldInjectedString, nullFoo.fieldInjectedStringProvider,
145         nullFoo.fieldInjectedNumberProvider);
146   }
147 
148   @Test
testNullability_reusuable()149   public void testNullability_reusuable() {
150     NullModule module = new NullModule();
151     NullComponent component =
152         DaggerNullabilityTest_NullComponent.builder().nullModule(module).build();
153 
154     // Test that the @Nullable @Reusuable binding is cached properly even when the value is null.
155     assertThat(module.integerCallCount).isEqualTo(0);
156     assertThat(component.integer()).isNull();
157     assertThat(module.integerCallCount).isEqualTo(1);
158     assertThat(component.integer()).isNull();
159     assertThat(module.integerCallCount).isEqualTo(1);
160   }
161 
162   @Test
testNullability_components()163   public void testNullability_components() {
164     NullComponent nullComponent = new NullComponent() {
165       @Override public Provider<String> stringProvider() {
166         return new Provider<String>() {
167           @Override public String get() {
168             return null;
169           }
170         };
171       }
172 
173       @Override public String string() {
174         return null;
175       }
176 
177       @Override public Provider<Number> numberProvider() {
178         return new Provider<Number>() {
179           @Override public Number get() {
180             return null;
181           }
182         };
183       }
184 
185       @Override public Number number() {
186         return null;
187       }
188 
189       @Override public NullFoo nullFoo() {
190         return null;
191       }
192 
193       @Override public Integer integer() {
194         return null;
195       }
196     };
197     NullComponentWithDependency component =
198         DaggerNullabilityTest_NullComponentWithDependency.builder()
199             .nullComponent(nullComponent)
200             .build();
201     validate(false, component.string(), component.stringProvider(), component.numberProvider());
202 
203     // Also validate that the component's number() method fails
204     try {
205       component.number();
206       fail();
207     } catch (NullPointerException npe) {
208       assertThat(npe)
209           .hasMessageThat()
210           .isEqualTo("Cannot return null from a non-@Nullable component method");
211     }
212   }
213 
validate(boolean fromProvides, String string, Provider<String> stringProvider, Provider<Number> numberProvider)214   private void validate(boolean fromProvides,
215       String string,
216       Provider<String> stringProvider,
217       Provider<Number> numberProvider) {
218     assertThat(string).isNull();
219     assertThat(numberProvider).isNotNull();
220     try {
221       numberProvider.get();
222       fail();
223     } catch (NullPointerException npe) {
224       assertThat(npe)
225           .hasMessageThat()
226           .isEqualTo(
227               "Cannot return null from a non-@Nullable "
228                   + (fromProvides ? "@Provides" : "component")
229                   + " method");
230     }
231     assertThat(stringProvider.get()).isNull();
232   }
233 }
234