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.internal.codegen; 18 19 import static com.google.testing.compile.CompilationSubject.assertThat; 20 import static dagger.internal.codegen.Compilers.daggerCompiler; 21 import static dagger.internal.codegen.TestUtils.message; 22 23 import com.google.testing.compile.Compilation; 24 import com.google.testing.compile.JavaFileObjects; 25 import javax.tools.JavaFileObject; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 import org.junit.runners.JUnit4; 29 30 @RunWith(JUnit4.class) 31 public class MissingBindingSuggestionsTest { injectable(String className, String constructorParams)32 private static JavaFileObject injectable(String className, String constructorParams) { 33 return JavaFileObjects.forSourceLines("test." + className, 34 "package test;", 35 "", 36 "import javax.inject.Inject;", 37 "", 38 "class " + className +" {", 39 " @Inject " + className + "(" + constructorParams + ") {}", 40 "}"); 41 } 42 emptyInterface(String interfaceName)43 private static JavaFileObject emptyInterface(String interfaceName) { 44 return JavaFileObjects.forSourceLines("test." + interfaceName, 45 "package test;", 46 "", 47 "import javax.inject.Inject;", 48 "", 49 "interface " + interfaceName +" {}"); 50 } 51 suggestsBindingInSeparateComponent()52 @Test public void suggestsBindingInSeparateComponent() { 53 JavaFileObject fooComponent = JavaFileObjects.forSourceLines("test.FooComponent", 54 "package test;", 55 "", 56 "import dagger.Subcomponent;", 57 "", 58 "@Subcomponent", 59 "interface FooComponent {", 60 " Foo getFoo();", 61 "}"); 62 JavaFileObject barModule = JavaFileObjects.forSourceLines("test.BarModule", 63 "package test;", 64 "", 65 "import dagger.Provides;", 66 "import javax.inject.Inject;", 67 "", 68 "@dagger.Module", 69 "final class BarModule {", 70 " @Provides Bar provideBar() {return null;}", 71 "}"); 72 JavaFileObject barComponent = JavaFileObjects.forSourceLines("test.BarComponent", 73 "package test;", 74 "", 75 "import dagger.Subcomponent;", 76 "", 77 "@Subcomponent(modules = {BarModule.class})", 78 "interface BarComponent {", 79 " Bar getBar();", 80 "}"); 81 JavaFileObject foo = injectable("Foo", "Bar bar"); 82 JavaFileObject bar = emptyInterface("Bar"); 83 84 JavaFileObject topComponent = JavaFileObjects.forSourceLines("test.TopComponent", 85 "package test;", 86 "", 87 "import dagger.Component;", 88 "", 89 "@Component", 90 "interface TopComponent {", 91 " FooComponent getFoo();", 92 " BarComponent getBar(BarModule barModule);", 93 "}"); 94 95 Compilation compilation = 96 daggerCompiler().compile(fooComponent, barComponent, topComponent, foo, bar, barModule); 97 assertThat(compilation).failed(); 98 assertThat(compilation).hadErrorCount(1); 99 assertThat(compilation) 100 .hadErrorContaining("A binding with matching key exists in component: BarComponent"); 101 } 102 suggestsBindingInNestedSubcomponent()103 @Test public void suggestsBindingInNestedSubcomponent() { 104 JavaFileObject fooComponent = JavaFileObjects.forSourceLines("test.FooComponent", 105 "package test;", 106 "", 107 "import dagger.Subcomponent;", 108 "", 109 "@Subcomponent", 110 "interface FooComponent {", 111 " Foo getFoo();", 112 "}"); 113 JavaFileObject barComponent = JavaFileObjects.forSourceLines("test.BarComponent", 114 "package test;", 115 "", 116 "import dagger.Subcomponent;", 117 "", 118 "@Subcomponent()", 119 "interface BarComponent {", 120 " BazComponent getBaz();", 121 "}"); 122 JavaFileObject bazModule = JavaFileObjects.forSourceLines("test.BazModule", 123 "package test;", 124 "", 125 "import dagger.Provides;", 126 "import javax.inject.Inject;", 127 "", 128 "@dagger.Module", 129 "final class BazModule {", 130 " @Provides Baz provideBaz() {return null;}", 131 "}"); 132 JavaFileObject bazComponent = JavaFileObjects.forSourceLines("test.BazComponent", 133 "package test;", 134 "", 135 "import dagger.Subcomponent;", 136 "", 137 "@Subcomponent(modules = {BazModule.class})", 138 "interface BazComponent {", 139 " Baz getBaz();", 140 "}"); 141 JavaFileObject foo = injectable("Foo", "Baz baz"); 142 JavaFileObject baz = emptyInterface("Baz"); 143 144 JavaFileObject topComponent = JavaFileObjects.forSourceLines("test.TopComponent", 145 "package test;", 146 "", 147 "import dagger.Component;", 148 "", 149 "@Component", 150 "interface TopComponent {", 151 " FooComponent getFoo();", 152 " BarComponent getBar();", 153 "}"); 154 155 Compilation compilation = 156 daggerCompiler() 157 .compile(fooComponent, barComponent, bazComponent, topComponent, foo, baz, bazModule); 158 assertThat(compilation).failed(); 159 assertThat(compilation).hadErrorCount(1); 160 assertThat(compilation) 161 .hadErrorContaining("A binding with matching key exists in component: BazComponent"); 162 } 163 164 @Test missingBindingInParentComponent()165 public void missingBindingInParentComponent() { 166 JavaFileObject parent = 167 JavaFileObjects.forSourceLines( 168 "Parent", 169 "import dagger.Component;", 170 "", 171 "@Component", 172 "interface Parent {", 173 " Foo foo();", 174 " Bar bar();", 175 " Child child();", 176 "}"); 177 JavaFileObject child = 178 JavaFileObjects.forSourceLines( 179 "Child", 180 "import dagger.Subcomponent;", 181 "", 182 "@Subcomponent(modules=BazModule.class)", 183 "interface Child {", 184 " Foo foo();", 185 " Baz baz();", 186 "}"); 187 JavaFileObject foo = 188 JavaFileObjects.forSourceLines( 189 "Foo", 190 "import javax.inject.Inject;", 191 "", 192 "class Foo {", 193 " @Inject Foo(Bar bar) {}", 194 "}"); 195 JavaFileObject bar = 196 JavaFileObjects.forSourceLines( 197 "Bar", 198 "import javax.inject.Inject;", 199 "", 200 "class Bar {", 201 " @Inject Bar(Baz baz) {}", 202 "}"); 203 JavaFileObject baz = JavaFileObjects.forSourceLines("Baz", "class Baz {}"); 204 JavaFileObject bazModule = JavaFileObjects.forSourceLines( 205 "BazModule", 206 "import dagger.Module;", 207 "import dagger.Provides;", 208 "import javax.inject.Inject;", 209 "", 210 "@Module", 211 "final class BazModule {", 212 " @Provides Baz provideBaz() {return new Baz();}", 213 "}"); 214 215 Compilation compilation = daggerCompiler().compile(parent, child, foo, bar, baz, bazModule); 216 assertThat(compilation).failed(); 217 assertThat(compilation).hadErrorCount(1); 218 assertThat(compilation) 219 .hadErrorContaining( 220 message( 221 "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an " 222 + "@Inject constructor or an @Provides-annotated method.", 223 "A binding with matching key exists in component: Child", 224 " Baz is injected at", 225 " Bar(baz)", 226 " Bar is requested at", 227 " Parent.bar()", 228 "The following other entry points also depend on it:", 229 " Parent.foo()", 230 " Child.foo() [Parent → Child]")) 231 .inFile(parent) 232 .onLineContaining("interface Parent"); 233 } 234 235 @Test missingBindingInSiblingComponent()236 public void missingBindingInSiblingComponent() { 237 JavaFileObject parent = 238 JavaFileObjects.forSourceLines( 239 "Parent", 240 "import dagger.Component;", 241 "", 242 "@Component", 243 "interface Parent {", 244 " Foo foo();", 245 " Bar bar();", 246 " Child1 child1();", 247 " Child2 child2();", 248 "}"); 249 JavaFileObject child1 = 250 JavaFileObjects.forSourceLines( 251 "Child1", 252 "import dagger.Subcomponent;", 253 "", 254 "@Subcomponent", 255 "interface Child1 {", 256 " Foo foo();", 257 " Baz baz();", 258 "}"); 259 JavaFileObject child2 = 260 JavaFileObjects.forSourceLines( 261 "Child2", 262 "import dagger.Subcomponent;", 263 "", 264 "@Subcomponent(modules = BazModule.class)", 265 "interface Child2 {", 266 " Foo foo();", 267 " Baz baz();", 268 "}"); 269 JavaFileObject foo = 270 JavaFileObjects.forSourceLines( 271 "Foo", 272 "import javax.inject.Inject;", 273 "", 274 "class Foo {", 275 " @Inject Foo(Bar bar) {}", 276 "}"); 277 JavaFileObject bar = 278 JavaFileObjects.forSourceLines( 279 "Bar", 280 "import javax.inject.Inject;", 281 "", 282 "class Bar {", 283 " @Inject Bar(Baz baz) {}", 284 "}"); 285 JavaFileObject baz = JavaFileObjects.forSourceLines("Baz", "class Baz {}"); 286 JavaFileObject bazModule = JavaFileObjects.forSourceLines( 287 "BazModule", 288 "import dagger.Module;", 289 "import dagger.Provides;", 290 "import javax.inject.Inject;", 291 "", 292 "@Module", 293 "final class BazModule {", 294 " @Provides Baz provideBaz() {return new Baz();}", 295 "}"); 296 297 Compilation compilation = 298 daggerCompiler().compile(parent, child1, child2, foo, bar, baz, bazModule); 299 assertThat(compilation).failed(); 300 assertThat(compilation).hadErrorCount(1); 301 assertThat(compilation) 302 .hadErrorContaining( 303 message( 304 "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an " 305 + "@Inject constructor or an @Provides-annotated method.", 306 "A binding with matching key exists in component: Child2", 307 " Baz is injected at", 308 " Bar(baz)", 309 " Bar is requested at", 310 " Parent.bar()", 311 "The following other entry points also depend on it:", 312 " Parent.foo()", 313 " Child1.foo() [Parent → Child1]", 314 " Child2.foo() [Parent → Child2]", 315 " Child1.baz() [Parent → Child1]")) 316 .inFile(parent) 317 .onLineContaining("interface Parent"); 318 } 319 } 320