xref: /aosp_15_r20/external/dagger2/javatests/dagger/functional/kotlinsrc/nullables/NullabilityTest.kt (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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 java.lang.NullPointerException
25 import javax.inject.Inject
26 import javax.inject.Provider
27 import org.junit.Assert.fail
28 import org.junit.Test
29 import org.junit.runner.RunWith
30 import org.junit.runners.JUnit4
31 
32 @RunWith(JUnit4::class)
33 class NullabilityTest {
34   @Component(dependencies = [NullComponent::class])
35   internal interface NullComponentWithDependency {
stringnull36     fun string(): String?
37     fun number(): Number
38     fun stringProvider(): Provider<String>
39     fun numberProvider(): Provider<Number>
40   }
41 
42   @Component(modules = [NullModule::class])
43   internal interface NullComponent {
44     fun string(): String?
45     fun integer(): Int?
46     fun nullFoo(): NullFoo
47     fun number(): Number
48     fun stringProvider(): Provider<String>
49     fun numberProvider(): Provider<Number>
50   }
51 
52   @Module
53   internal class NullModule {
54     var numberValue: Number? = null
55     var integerCallCount = 0
56 
provideNullableStringnull57     @Provides fun provideNullableString(): String? = null
58 
59     @Provides fun provideNumber(): Number = numberValue!!
60 
61     @Provides
62     @Reusable
63     fun provideNullReusableInteger(): Int? {
64       integerCallCount++
65       return null
66     }
67   }
68 
69   @Suppress("BadInject") // This is just for testing purposes.
70   internal class NullFoo
71   @Inject
72   constructor(
73     val string: String?,
74     val number: Number,
75     val stringProvider: Provider<String>,
76     val numberProvider: Provider<Number>
77   ) {
78     var methodInjectedString: String? = null
79     lateinit var methodInjectedNumber: Number
80     lateinit var methodInjectedStringProvider: Provider<String>
81     lateinit var methodInjectedNumberProvider: Provider<Number>
82 
83     @Inject
injectnull84     fun inject(
85       string: String?,
86       number: Number,
87       stringProvider: Provider<String>,
88       numberProvider: Provider<Number>
89     ) {
90       methodInjectedString = string
91       methodInjectedNumber = number
92       methodInjectedStringProvider = stringProvider
93       methodInjectedNumberProvider = numberProvider
94     }
95 
96     @JvmField @Inject var fieldInjectedString: String? = null
97     @Inject lateinit var fieldInjectedNumber: Number
98     @Inject lateinit var fieldInjectedStringProvider: Provider<String>
99     @Inject lateinit var fieldInjectedNumberProvider: Provider<Number>
100   }
101 
102   @Test
testNullability_providesnull103   fun testNullability_provides() {
104     val module = NullModule()
105     val component = DaggerNullabilityTest_NullComponent.builder().nullModule(module).build()
106 
107     // Can't construct NullFoo because it depends on Number, and Number was null.
108     try {
109       component.nullFoo()
110       fail()
111     } catch (npe: NullPointerException) {
112       // NOTE: In Java we would check that the Dagger error message is something like:
113       //   "Cannot return null from a non-@Nullable @Provides method"
114       // However, in Kotlin there's no way to return a null value from a non-null return type
115       // without explicitly using `!!`, which results in an error before Dagger's runtime
116       // checkNotNull has a chance to run.
117     }
118 
119     // set number to non-null so we can create
120     module.numberValue = 1
121     val nullFoo = component.nullFoo()
122 
123     // Then set it back to null so we can test its providers.
124     module.numberValue = null
125     validate(nullFoo.string, nullFoo.stringProvider, nullFoo.numberProvider)
126     validate(
127       nullFoo.methodInjectedString,
128       nullFoo.methodInjectedStringProvider,
129       nullFoo.methodInjectedNumberProvider
130     )
131     validate(
132       nullFoo.fieldInjectedString,
133       nullFoo.fieldInjectedStringProvider,
134       nullFoo.fieldInjectedNumberProvider
135     )
136   }
137 
138   @Test
testNullability_reusuablenull139   fun testNullability_reusuable() {
140     val module = NullModule()
141     val component = DaggerNullabilityTest_NullComponent.builder().nullModule(module).build()
142 
143     // Test that the @Nullable @Reusuable binding is cached properly even when the value is null.
144     assertThat(module.integerCallCount).isEqualTo(0)
145     assertThat(component.integer()).isNull()
146     assertThat(module.integerCallCount).isEqualTo(1)
147     assertThat(component.integer()).isNull()
148     assertThat(module.integerCallCount).isEqualTo(1)
149   }
150 
151   @Test
testNullability_componentsnull152   fun testNullability_components() {
153     val nullComponent: NullComponent =
154       object : NullComponent {
155         override fun string(): String? = null
156         override fun integer(): Int? = null
157         override fun stringProvider(): Provider<String> = Provider { null!! }
158         override fun numberProvider(): Provider<Number> = Provider { null!! }
159         override fun number(): Number = null!!
160         override fun nullFoo(): NullFoo = null!!
161       }
162     val component =
163       DaggerNullabilityTest_NullComponentWithDependency.builder()
164         .nullComponent(nullComponent)
165         .build()
166     validate(component.string(), component.stringProvider(), component.numberProvider())
167 
168     // Also validate that the component's number() method fails
169     try {
170       component.number()
171       fail()
172     } catch (npe: NullPointerException) {
173       // NOTE: In Java we would check that the Dagger error message is something like:
174       //   "Cannot return null from a non-@Nullable @Provides method"
175       // However, in Kotlin there's no way to return a null value from a non-null return type
176       // without explicitly using `!!`, which results in an error before Dagger's runtime
177       // checkNotNull has a chance to run.
178     }
179   }
180 
validatenull181   private fun validate(
182     string: String?,
183     stringProvider: Provider<String>,
184     numberProvider: Provider<Number>
185   ) {
186     assertThat(string).isNull()
187     assertThat(numberProvider).isNotNull()
188     try {
189       numberProvider.get()
190       fail()
191     } catch (npe: NullPointerException) {
192       // NOTE: In Java we would check that the Dagger error message is something like:
193       //   "Cannot return null from a non-@Nullable @Provides method"
194       // However, in Kotlin there's no way to return a null value from a non-null return type
195       // without explicitly using `!!`, which results in an error before Dagger's runtime
196       // checkNotNull has a chance to run.
197     }
198     assertThat(stringProvider.get()).isNull()
199   }
200 }
201