• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }