1 package org.robolectric.util.inject; 2 3 import static com.google.common.truth.Truth.assertThat; 4 import static org.junit.Assert.fail; 5 6 import com.google.auto.service.AutoService; 7 import java.util.ArrayList; 8 import java.util.Arrays; 9 import java.util.List; 10 import java.util.stream.Collectors; 11 import javax.annotation.Priority; 12 import javax.inject.Inject; 13 import javax.inject.Named; 14 import org.junit.Before; 15 import org.junit.Ignore; 16 import org.junit.Test; 17 import org.junit.runner.RunWith; 18 import org.junit.runners.JUnit4; 19 20 @RunWith(JUnit4.class) 21 public class InjectorTest { 22 23 private Injector.Builder builder; 24 private Injector injector; 25 private final List<Class<?>> pluginClasses = new ArrayList<>(); 26 27 @Before setUp()28 public void setUp() throws Exception { 29 builder = new Injector.Builder(); 30 injector = builder.build(); 31 } 32 33 @Test whenImplSpecified_shouldProvideInstance()34 public void whenImplSpecified_shouldProvideInstance() throws Exception { 35 injector = builder.bind(Thing.class, MyThing.class).build(); 36 37 assertThat(injector.getInstance(Thing.class)) 38 .isInstanceOf(MyThing.class); 39 } 40 41 @Test whenImplSpecified_shouldUseSameInstance()42 public void whenImplSpecified_shouldUseSameInstance() throws Exception { 43 injector = builder.bind(Thing.class, MyThing.class).build(); 44 45 Thing thing = injector.getInstance(Thing.class); 46 assertThat(injector.getInstance(Thing.class)).isSameInstanceAs(thing); 47 } 48 49 @Test whenServiceSpecified_shouldProvideInstance()50 public void whenServiceSpecified_shouldProvideInstance() throws Exception { 51 assertThat(injector.getInstance(Thing.class)) 52 .isInstanceOf(ThingFromServiceConfig.class); 53 } 54 55 @Test whenServiceSpecified_shouldUseSameInstance()56 public void whenServiceSpecified_shouldUseSameInstance() throws Exception { 57 Thing thing = injector.getInstance(Thing.class); 58 assertThat(injector.getInstance(Thing.class)).isSameInstanceAs(thing); 59 } 60 61 @Test whenConcreteClassRequested_shouldProvideInstance()62 public void whenConcreteClassRequested_shouldProvideInstance() throws Exception { 63 assertThat(injector.getInstance(MyUmm.class)) 64 .isInstanceOf(MyUmm.class); 65 } 66 67 @Test whenDefaultSpecified_shouldProvideInstance()68 public void whenDefaultSpecified_shouldProvideInstance() throws Exception { 69 injector = builder.bindDefault(Umm.class, MyUmm.class).build(); 70 71 assertThat(injector.getInstance(Umm.class)) 72 .isInstanceOf(MyUmm.class); 73 } 74 75 @Test whenDefaultSpecified_shouldUseSameInstance()76 public void whenDefaultSpecified_shouldUseSameInstance() throws Exception { 77 Thing thing = injector.getInstance(Thing.class); 78 assertThat(injector.getInstance(Thing.class)).isSameInstanceAs(thing); 79 } 80 81 @Test whenNoImplOrServiceOrDefaultSpecified_shouldThrow()82 public void whenNoImplOrServiceOrDefaultSpecified_shouldThrow() throws Exception { 83 try { 84 injector.getInstance(Umm.class); 85 fail(); 86 } catch (InjectionException e) { 87 // ok 88 } 89 } 90 91 @Test registerDefaultService_providesFallbackImplOnlyIfNoServiceSpecified()92 public void registerDefaultService_providesFallbackImplOnlyIfNoServiceSpecified() 93 throws Exception { 94 builder.bindDefault(Thing.class, MyThing.class); 95 96 assertThat(injector.getInstance(Thing.class)) 97 .isInstanceOf(ThingFromServiceConfig.class); 98 99 builder.bindDefault(Umm.class, MyUmm.class); 100 assertThat(injector.getInstance(Thing.class)) 101 .isInstanceOf(ThingFromServiceConfig.class); 102 } 103 104 @Test shouldPreferSingularPublicConstructorAnnotatedInject()105 public void shouldPreferSingularPublicConstructorAnnotatedInject() throws Exception { 106 injector = builder 107 .bind(Thing.class, MyThing.class) 108 .bind(Umm.class, MyUmm.class) 109 .build(); 110 111 Umm umm = injector.getInstance(Umm.class); 112 assertThat(umm).isNotNull(); 113 assertThat(umm).isInstanceOf(MyUmm.class); 114 115 MyUmm myUmm = (MyUmm) umm; 116 assertThat(myUmm.thing).isNotNull(); 117 assertThat(myUmm.thing).isInstanceOf(MyThing.class); 118 119 assertThat(myUmm.thing).isSameInstanceAs(injector.getInstance(Thing.class)); 120 } 121 122 @Test shouldAcceptSingularPublicConstructorWithoutInjectAnnotation()123 public void shouldAcceptSingularPublicConstructorWithoutInjectAnnotation() throws Exception { 124 injector = builder 125 .bind(Thing.class, MyThing.class) 126 .bind(Umm.class, MyUmmNoInject.class) 127 .build(); 128 129 Umm umm = injector.getInstance(Umm.class); 130 assertThat(umm).isNotNull(); 131 assertThat(umm).isInstanceOf(MyUmmNoInject.class); 132 133 MyUmmNoInject myUmm = (MyUmmNoInject) umm; 134 assertThat(myUmm.thing).isNotNull(); 135 assertThat(myUmm.thing).isInstanceOf(MyThing.class); 136 137 assertThat(myUmm.thing).isSameInstanceAs(injector.getInstance(Thing.class)); 138 } 139 140 @Test whenArrayRequested_mayReturnMultiplePlugins()141 public void whenArrayRequested_mayReturnMultiplePlugins() throws Exception { 142 MultiThing[] multiThings = injector.getInstance(MultiThing[].class); 143 144 // X comes first because it has a higher priority 145 assertThat(classesOf(multiThings)) 146 .containsExactly(MultiThingX.class, MultiThingA.class).inOrder(); 147 } 148 149 @Test whenCollectionRequested_mayReturnMultiplePlugins()150 public void whenCollectionRequested_mayReturnMultiplePlugins() throws Exception { 151 ThingRequiringMultiThings it = injector.getInstance(ThingRequiringMultiThings.class); 152 153 // X comes first because it has a higher priority 154 assertThat(classesOf(it.multiThings)) 155 .containsExactly(MultiThingX.class, MultiThingA.class).inOrder(); 156 } 157 158 @Test whenListRequested_itIsUnmodifiable()159 public void whenListRequested_itIsUnmodifiable() throws Exception { 160 ThingRequiringMultiThings it = injector.getInstance(ThingRequiringMultiThings.class); 161 162 try { 163 it.multiThings.clear(); 164 fail(); 165 } catch (Exception e) { 166 assertThat(e).isInstanceOf(UnsupportedOperationException.class); 167 } 168 } 169 autoFactory_factoryMethodsCreateNewInstances()170 @Test public void autoFactory_factoryMethodsCreateNewInstances() throws Exception { 171 injector = builder.bind(Umm.class, MyUmm.class).build(); 172 FooFactory factory = injector.getInstance(FooFactory.class); 173 Foo chauncey = factory.create("Chauncey"); 174 assertThat(chauncey.name).isEqualTo("Chauncey"); 175 176 Foo anotherChauncey = factory.create("Chauncey"); 177 assertThat(anotherChauncey).isNotSameInstanceAs(chauncey); 178 } 179 autoFactory_injectedValuesComeFromSuperInjector()180 @Test public void autoFactory_injectedValuesComeFromSuperInjector() throws Exception { 181 injector = builder.bind(Umm.class, MyUmm.class).build(); 182 FooFactory factory = injector.getInstance(FooFactory.class); 183 Foo chauncey = factory.create("Chauncey"); 184 assertThat(chauncey.thing).isSameInstanceAs(injector.getInstance(Thing.class)); 185 } 186 whenFactoryRequested_createsInjectedFactory()187 @Test public void whenFactoryRequested_createsInjectedFactory() throws Exception { 188 injector = builder.bind(Umm.class, MyUmm.class).build(); 189 FooFactory factory = injector.getInstance(FooFactory.class); 190 Foo chauncey = factory.create("Chauncey"); 191 assertThat(chauncey.name).isEqualTo("Chauncey"); 192 193 Foo anotherChauncey = factory.create("Chauncey"); 194 assertThat(anotherChauncey).isNotSameInstanceAs(chauncey); 195 196 assertThat(chauncey.thing).isSameInstanceAs(injector.getInstance(Thing.class)); 197 } 198 scopedInjector_shouldCheckParentBeforeProvidingDefault()199 @Test public void scopedInjector_shouldCheckParentBeforeProvidingDefault() throws Exception { 200 injector = builder.build(); 201 Injector subInjector = new Injector.Builder(injector).build(); 202 203 MyUmm subUmm = subInjector.getInstance(MyUmm.class); 204 assertThat(injector.getInstance(MyUmm.class)).isSameInstanceAs(subUmm); 205 } 206 shouldInjectByNamedKeys()207 @Test public void shouldInjectByNamedKeys() throws Exception { 208 injector = builder 209 .bind(new Injector.Key<>(String.class, "namedThing"), "named value") 210 .bind(String.class, "unnamed value") 211 .build(); 212 NamedParams namedParams = injector.getInstance(NamedParams.class); 213 assertThat(namedParams.withName).isEqualTo("named value"); 214 assertThat(namedParams.withoutName).isEqualTo("unnamed value"); 215 } 216 shouldPreferPluginsOverConcreteClass()217 @Test public void shouldPreferPluginsOverConcreteClass() throws Exception { 218 PluginFinder pluginFinder = new PluginFinder(new MyServiceFinderAdapter(pluginClasses)); 219 Injector injector = new Injector.Builder(null, pluginFinder).build(); 220 pluginClasses.add(SubclassOfConcreteThing.class); 221 ConcreteThing instance = injector.getInstance(ConcreteThing.class); 222 assertThat(instance.getClass()).isEqualTo(SubclassOfConcreteThing.class); 223 } 224 225 @Test subInjectorIsUsedForResolvingTransitiveDependencies()226 public void subInjectorIsUsedForResolvingTransitiveDependencies() throws Exception { 227 FakeSandboxManager sandboxManager = injector.getInstance(FakeSandboxManager.class); 228 FakeSdk runtimeSdk = new FakeSdk("runtime"); 229 FakeSdk compileSdk = new FakeSdk("compile"); 230 FakeSandbox sandbox = sandboxManager.getSandbox(runtimeSdk, compileSdk); 231 assertThat(sandbox.runtimeSdk).isSameInstanceAs(runtimeSdk); 232 assertThat(sandbox.compileSdk).isSameInstanceAs(compileSdk); 233 } 234 235 @Test @Ignore("todo") objectsCreatedByFactoryShareTransitiveDependencies()236 public void objectsCreatedByFactoryShareTransitiveDependencies() throws Exception { 237 FakeSandboxManager sandboxManager = injector.getInstance(FakeSandboxManager.class); 238 FakeSdk runtimeSdk = new FakeSdk("runtime"); 239 FakeSdk compileASdk = new FakeSdk("compileA"); 240 FakeSdk compileBSdk = new FakeSdk("compileB"); 241 FakeSandbox sandboxA = sandboxManager.getSandbox(runtimeSdk, compileASdk); 242 FakeSandbox sandboxB = sandboxManager.getSandbox(runtimeSdk, compileBSdk); 243 assertThat(sandboxA.sandboxClassLoader).isSameInstanceAs(sandboxB.sandboxClassLoader); 244 } 245 246 @Test shouldProvideDecentErrorMessages()247 public void shouldProvideDecentErrorMessages() throws Exception { 248 FakeSandboxManager sandboxManager = injector.getInstance(FakeSandboxManager.class); 249 Exception actualException = null; 250 try { 251 sandboxManager.brokenGetSandbox(); 252 fail(); 253 } catch (Exception e) { 254 actualException = e; 255 } 256 assertThat(actualException.getMessage()) 257 .contains("Failed to resolve dependency: FakeSandbox/FakeSdk/String"); 258 } 259 260 @Test @Ignore("todo") shouldOnlyAttemptToResolveTypesKnownToClassLoader()261 public void shouldOnlyAttemptToResolveTypesKnownToClassLoader() throws Exception { 262 } 263 264 ///////////////////////////// 265 classesOf(Object[] items)266 private List<? extends Class<?>> classesOf(Object[] items) { 267 return classesOf(Arrays.asList(items)); 268 } 269 classesOf(List<?> items)270 private List<? extends Class<?>> classesOf(List<?> items) { 271 return items.stream().map(Object::getClass).collect(Collectors.toList()); 272 } 273 274 /** A thing. */ 275 public interface Thing { 276 } 277 278 public static class MyThing implements Thing { 279 } 280 281 public static class ConcreteThing { 282 } 283 284 public static class SubclassOfConcreteThing extends ConcreteThing { 285 } 286 287 /** Class for test. */ 288 @AutoService(Thing.class) 289 public static class ThingFromServiceConfig implements Thing { 290 } 291 292 private interface Umm { 293 294 } 295 296 public static class MyUmm implements Umm { 297 298 private final Thing thing; 299 300 @Inject MyUmm(Thing thing)301 public MyUmm(Thing thing) { 302 this.thing = thing; 303 } 304 305 @SuppressWarnings("unused") MyUmm(String thingz)306 public MyUmm(String thingz) { 307 this.thing = null; 308 } 309 } 310 311 /** Class for test. */ 312 public static class MyUmmNoInject implements Umm { 313 314 private final Thing thing; 315 MyUmmNoInject(Thing thing)316 public MyUmmNoInject(Thing thing) { 317 this.thing = thing; 318 } 319 } 320 321 private interface MultiThing { 322 323 } 324 325 /** Class for test. */ 326 @Priority(-5) 327 @AutoService(MultiThing.class) 328 public static class MultiThingA implements MultiThing { 329 } 330 331 /** Class for test. */ 332 @AutoService(MultiThing.class) 333 public static class MultiThingX implements MultiThing { 334 } 335 336 /** Class for test. */ 337 public static class ThingRequiringMultiThings { 338 339 private List<MultiThing> multiThings; 340 ThingRequiringMultiThings(List<MultiThing> multiThings)341 public ThingRequiringMultiThings(List<MultiThing> multiThings) { 342 this.multiThings = multiThings; 343 } 344 } 345 346 static class Foo { 347 348 private final Thing thing; 349 private final Umm umm; 350 private final String name; 351 Foo(Thing thing, Umm umm, String name)352 public Foo(Thing thing, Umm umm, String name) { 353 this.thing = thing; 354 this.umm = umm; 355 this.name = name; 356 } 357 } 358 359 @AutoFactory 360 interface FooFactory { create(String name)361 Foo create(String name); 362 } 363 364 static class NamedParams { 365 366 private final Thing thing; 367 private final String withName; 368 private final String withoutName; 369 NamedParams(Thing thing, @Named("namedThing") String withName, String withoutName)370 public NamedParams(Thing thing, @Named("namedThing") String withName, String withoutName) { 371 this.thing = thing; 372 this.withName = withName; 373 this.withoutName = withoutName; 374 } 375 } 376 377 static class FakeSdk { 378 private final String name; 379 FakeSdk(String name)380 public FakeSdk(String name) { 381 this.name = name; 382 } 383 } 384 385 static class FakeSandbox { 386 387 private final FakeSdk runtimeSdk; 388 private final FakeSdk compileSdk; 389 private final FakeSandboxClassLoader sandboxClassLoader; 390 FakeSandbox( @amed"runtimeSdk") FakeSdk runtimeSdk, @Named("compileSdk") FakeSdk compileSdk, FakeSandboxClassLoader sandboxClassLoader)391 public FakeSandbox( 392 @Named("runtimeSdk") FakeSdk runtimeSdk, 393 @Named("compileSdk") FakeSdk compileSdk, 394 FakeSandboxClassLoader sandboxClassLoader) { 395 this.runtimeSdk = runtimeSdk; 396 this.compileSdk = compileSdk; 397 this.sandboxClassLoader = sandboxClassLoader; 398 } 399 } 400 401 static class FakeSandboxClassLoader { 402 private final FakeSdk runtimeSdk; 403 FakeSandboxClassLoader(@amed"runtimeSdk") FakeSdk runtimeSdk)404 public FakeSandboxClassLoader(@Named("runtimeSdk") FakeSdk runtimeSdk) { 405 this.runtimeSdk = runtimeSdk; 406 } 407 } 408 409 static class FakeSandboxManager { 410 411 private final FakeSandboxFactory sandboxFactory; 412 FakeSandboxManager(FakeSandboxFactory sandboxFactory)413 public FakeSandboxManager(FakeSandboxFactory sandboxFactory) { 414 this.sandboxFactory = sandboxFactory; 415 } 416 getSandbox(FakeSdk runtimeSdk, FakeSdk compileSdk)417 public FakeSandbox getSandbox(FakeSdk runtimeSdk, FakeSdk compileSdk) { 418 return sandboxFactory.createSandbox(runtimeSdk, compileSdk); 419 } 420 brokenGetSandbox()421 public FakeSandbox brokenGetSandbox() { 422 return sandboxFactory.createSandbox(); 423 } 424 } 425 426 @AutoFactory 427 private interface FakeSandboxFactory { createSandbox(@amed"runtimeSdk") FakeSdk runtimeSdk, @Named("compileSdk") FakeSdk compileSdk)428 FakeSandbox createSandbox(@Named("runtimeSdk") FakeSdk runtimeSdk, 429 @Named("compileSdk") FakeSdk compileSdk); createSandbox()430 FakeSandbox createSandbox(); 431 } 432 }