• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.kotlinsrc.nullables
18 
19 import com.google.common.truth.Truth.assertThat
20 import dagger.Component
21 import dagger.Module
22 import dagger.Provides
23 import dagger.Reusable
24 import dagger.multibindings.IntoSet
25 import java.lang.NullPointerException
26 import javax.inject.Inject
27 import javax.inject.Provider
28 import javax.inject.Singleton
29 import org.junit.Assert.fail
30 import org.junit.Test
31 import org.junit.runner.RunWith
32 import org.junit.runners.JUnit4
33 
34 @RunWith(JUnit4::class)
35 class NullabilityTest {
36   @Component(dependencies = [NullComponent::class])
37   internal interface NullComponentWithDependency {
stringnull38     fun string(): String?
39 
40     fun number(): Number
41 
42     fun stringProvider(): Provider<String>
43 
44     fun numberProvider(): Provider<Number>
45   }
46 
47   interface Bar<T> {}
48 
49   class BarNoNull : Bar<String>
50 
51   class BarWithNull : Bar<String?>
52 
53   @Component(modules = [NullModule::class])
54   internal interface NullComponent {
stringnull55     fun string(): String?
56 
57     fun integer(): Int?
58 
59     fun nullFoo(): NullFoo
60 
61     fun number(): Number
62 
63     fun stringProvider(): Provider<String>
64 
65     fun numberProvider(): Provider<Number>
66 
67     fun setOfBar(): Set<Bar<String>>
68 
69     fun setOfBarWithNullableArg(): Set<Bar<String?>>
70   }
71 
72   @Module
73   internal class NullModule {
74     var numberValue: Number? = null
75     var integerCallCount = 0
76 
77     @IntoSet @Provides fun providesNullableStringInToSet(): Bar<String?> = BarWithNull()
78 
79     @IntoSet @Provides fun providesNonNullStringIntoSet(): Bar<String> = BarNoNull()
80 
81     @Provides fun provideNullableString(): String? = null
82 
83     @Provides fun provideNumber(): Number = numberValue!!
84 
85     @Provides
86     @Reusable
87     fun provideNullReusableInteger(): Int? {
88       integerCallCount++
89       return null
90     }
91   }
92 
93   @Suppress("BadInject") // This is just for testing purposes.
94   internal class NullFoo
95   @Inject
96   constructor(
97     val string: String?,
98     val number: Number,
99     val stringProvider: Provider<String>,
100     val numberProvider: Provider<Number>,
101   ) {
102     var methodInjectedString: String? = null
103     lateinit var methodInjectedNumber: Number
104     lateinit var methodInjectedStringProvider: Provider<String>
105     lateinit var methodInjectedNumberProvider: Provider<Number>
106 
107     @Inject
injectnull108     fun inject(
109       string: String?,
110       number: Number,
111       stringProvider: Provider<String>,
112       numberProvider: Provider<Number>,
113     ) {
114       methodInjectedString = string
115       methodInjectedNumber = number
116       methodInjectedStringProvider = stringProvider
117       methodInjectedNumberProvider = numberProvider
118     }
119 
120     @JvmField @Inject var fieldInjectedString: String? = null
121     @Inject lateinit var fieldInjectedNumber: Number
122     @Inject lateinit var fieldInjectedStringProvider: Provider<String>
123     @Inject lateinit var fieldInjectedNumberProvider: Provider<Number>
124   }
125 
126   @Test
testNullability_providesnull127   fun testNullability_provides() {
128     val module = NullModule()
129     val component = DaggerNullabilityTest_NullComponent.builder().nullModule(module).build()
130 
131     // Can't construct NullFoo because it depends on Number, and Number was null.
132     try {
133       component.nullFoo()
134       fail()
135     } catch (npe: NullPointerException) {
136       // NOTE: In Java we would check that the Dagger error message is something like:
137       //   "Cannot return null from a non-@Nullable @Provides method"
138       // However, in Kotlin there's no way to return a null value from a non-null return type
139       // without explicitly using `!!`, which results in an error before Dagger's runtime
140       // checkNotNull has a chance to run.
141     }
142 
143     // set number to non-null so we can create
144     module.numberValue = 1
145     val nullFoo = component.nullFoo()
146 
147     // Then set it back to null so we can test its providers.
148     module.numberValue = null
149     validate(nullFoo.string, nullFoo.stringProvider, nullFoo.numberProvider)
150     validate(
151       nullFoo.methodInjectedString,
152       nullFoo.methodInjectedStringProvider,
153       nullFoo.methodInjectedNumberProvider,
154     )
155     validate(
156       nullFoo.fieldInjectedString,
157       nullFoo.fieldInjectedStringProvider,
158       nullFoo.fieldInjectedNumberProvider,
159     )
160   }
161 
162   @Test
testNullability_reusuablenull163   fun testNullability_reusuable() {
164     val module = NullModule()
165     val component = DaggerNullabilityTest_NullComponent.builder().nullModule(module).build()
166 
167     // Test that the @Nullable @Reusuable binding is cached properly even when the value is null.
168     assertThat(module.integerCallCount).isEqualTo(0)
169     assertThat(component.integer()).isNull()
170     assertThat(module.integerCallCount).isEqualTo(1)
171     assertThat(component.integer()).isNull()
172     assertThat(module.integerCallCount).isEqualTo(1)
173     assertThat(component.setOfBar().size).isEqualTo(2)
174     assertThat(component.setOfBarWithNullableArg().size).isEqualTo(2)
175   }
176 
177   @Test
testNullability_componentsnull178   fun testNullability_components() {
179     val nullComponent: NullComponent =
180       object : NullComponent {
181         override fun string(): String? = null
182 
183         override fun integer(): Int? = null
184 
185         override fun stringProvider(): Provider<String> = Provider { null!! }
186 
187         override fun numberProvider(): Provider<Number> = Provider { null!! }
188 
189         override fun number(): Number = null!!
190 
191         override fun nullFoo(): NullFoo = null!!
192 
193         override fun setOfBar(): Set<Bar<String>> = emptySet()
194 
195         override fun setOfBarWithNullableArg(): Set<Bar<String?>> = emptySet()
196       }
197     val component =
198       DaggerNullabilityTest_NullComponentWithDependency.builder()
199         .nullComponent(nullComponent)
200         .build()
201     validate(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 (npe: NullPointerException) {
208       // NOTE: In Java we would check that the Dagger error message is something like:
209       //   "Cannot return null from a non-@Nullable @Provides method"
210       // However, in Kotlin there's no way to return a null value from a non-null return type
211       // without explicitly using `!!`, which results in an error before Dagger's runtime
212       // checkNotNull has a chance to run.
213     }
214   }
215 
validatenull216   private fun validate(
217     string: String?,
218     stringProvider: Provider<String>,
219     numberProvider: Provider<Number>,
220   ) {
221     assertThat(string).isNull()
222     assertThat(numberProvider).isNotNull()
223     try {
224       numberProvider.get()
225       fail()
226     } catch (npe: NullPointerException) {
227       // NOTE: In Java we would check that the Dagger error message is something like:
228       //   "Cannot return null from a non-@Nullable @Provides method"
229       // However, in Kotlin there's no way to return a null value from a non-null return type
230       // without explicitly using `!!`, which results in an error before Dagger's runtime
231       // checkNotNull has a chance to run.
232     }
233     assertThat(stringProvider.get()).isNull()
234   }
235 }
236