1 /*
2  * Copyright (C) 2021 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.hilt.android;
18 
19 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
20 import static com.google.common.truth.Truth.assertThat;
21 import static org.junit.Assert.assertThrows;
22 
23 import android.os.Build;
24 import androidx.test.ext.junit.runners.AndroidJUnit4;
25 import dagger.hilt.EntryPoint;
26 import dagger.hilt.EntryPoints;
27 import dagger.hilt.InstallIn;
28 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.BarEntryPoint;
29 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyFooEntryPoint;
30 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyMySubcomponentBuilderEntryPoint;
31 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponent;
32 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentBuilderEntryPoint;
33 import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentScoped;
34 import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
35 import dagger.hilt.android.testing.HiltAndroidRule;
36 import dagger.hilt.android.testing.HiltAndroidTest;
37 import dagger.hilt.android.testing.HiltTestApplication;
38 import dagger.hilt.components.SingletonComponent;
39 import javax.inject.Inject;
40 import javax.inject.Singleton;
41 import org.junit.Rule;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.robolectric.annotation.Config;
45 
46 @HiltAndroidTest
47 @RunWith(AndroidJUnit4.class)
48 // Robolectric requires Java9 to run API 29 and above, so use API 28 instead
49 @Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
50 public final class EarlyEntryPointHiltAndroidTestRuntimeTest {
51   @EntryPoint
52   @InstallIn(SingletonComponent.class)
53   interface FooEntryPoint {
foo()54     Foo foo();
55   }
56 
57   @Singleton
58   public static class Foo {
59     @Inject
Foo()60     Foo() {}
61   }
62 
63   @MySubcomponentScoped
64   public static class Bar {
65     final Foo foo;
66     final int id;
67 
68     @Inject
Bar(Foo foo, int id)69     Bar(Foo foo, int id) {
70       this.foo = foo;
71       this.id = id;
72     }
73   }
74 
75   @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
76 
77   @Test
testEarlyEntryPointsWrongEntryPointFails()78   public void testEarlyEntryPointsWrongEntryPointFails() throws Exception {
79     IllegalStateException exception =
80         assertThrows(
81             IllegalStateException.class,
82             () -> EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class));
83 
84     assertThat(exception)
85         .hasMessageThat()
86         .contains(
87             "FooEntryPoint should be called with EntryPoints.get() rather than "
88                 + "EarlyEntryPoints.get()");
89   }
90 
91   @Test
testEntryPointsWrongEntryPointFails()92   public void testEntryPointsWrongEntryPointFails() throws Exception {
93     IllegalStateException exception =
94         assertThrows(
95             IllegalStateException.class,
96             () -> EntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class));
97 
98     assertThat(exception)
99         .hasMessageThat()
100         .contains(
101             "Interface, dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses."
102                 + "EarlyFooEntryPoint, annotated with @EarlyEntryPoint should be called with "
103                 + "EarlyEntryPoints.get() rather than EntryPoints.get()");
104   }
105 
106   @Test
testComponentInstances()107   public void testComponentInstances() {
108     Object componentManager = ((HiltTestApplication) getApplicationContext()).componentManager();
109     Object component1 = ((TestApplicationComponentManager) componentManager).generatedComponent();
110     Object component2 = ((TestApplicationComponentManager) componentManager).generatedComponent();
111     assertThat(component1).isNotNull();
112     assertThat(component2).isNotNull();
113     assertThat(component1).isEqualTo(component2);
114 
115     Object earlyComponent1 =
116         ((TestApplicationComponentManager) componentManager).earlySingletonComponent();
117     Object earlyComponent2 =
118         ((TestApplicationComponentManager) componentManager).earlySingletonComponent();
119     assertThat(earlyComponent1).isNotNull();
120     assertThat(earlyComponent2).isNotNull();
121     assertThat(earlyComponent1).isEqualTo(earlyComponent2);
122     assertThat(earlyComponent1).isNotEqualTo(component1);
123   }
124 
125   // Test that the early entry point returns a different @Singleton binding instance.
126   @Test
testScopedEntryPointValues()127   public void testScopedEntryPointValues() {
128     Foo foo1 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
129     Foo foo2 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
130     Foo earlyFoo1 =
131         EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
132     Foo earlyFoo2 =
133         EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
134 
135     assertThat(foo1).isNotNull();
136     assertThat(foo2).isNotNull();
137     assertThat(earlyFoo1).isNotNull();
138     assertThat(earlyFoo2).isNotNull();
139 
140     assertThat(foo1).isEqualTo(foo2);
141     assertThat(earlyFoo1).isEqualTo(earlyFoo2);
142     assertThat(earlyFoo1).isNotEqualTo(foo1);
143   }
144 
145   // Test that the a subcomponent of the early component does not need to use EarlyEntryPoints.
146   @Test
testSubcomponentEntryPoints()147   public void testSubcomponentEntryPoints() {
148     MySubcomponent subcomponent =
149         EntryPoints.get(getApplicationContext(), MySubcomponentBuilderEntryPoint.class)
150             .mySubcomponentBuilder()
151             .id(5)
152             .build();
153 
154     MySubcomponent earlySubcomponent =
155         EarlyEntryPoints.get(
156                 getApplicationContext(), EarlyMySubcomponentBuilderEntryPoint.class)
157             .mySubcomponentBuilder()
158             .id(11)
159             .build();
160 
161     assertThat(subcomponent).isNotNull();
162     assertThat(earlySubcomponent).isNotNull();
163     assertThat(subcomponent).isNotEqualTo(earlySubcomponent);
164 
165     // Test that subcomponents can use EntryPoints
166     Bar bar1 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar();
167     Bar bar2 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar();
168 
169     // Test that early subcomponents can use EntryPoints or EarlyEntryPoints
170     Bar earlyBar1 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar();
171     Bar earlyBar2 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar();
172 
173     assertThat(bar1).isNotNull();
174     assertThat(bar2).isNotNull();
175     assertThat(earlyBar1).isNotNull();
176     assertThat(earlyBar2).isNotNull();
177     assertThat(bar1).isEqualTo(bar2);
178     assertThat(earlyBar1).isEqualTo(earlyBar2);
179     assertThat(bar1).isNotEqualTo(earlyBar1);
180   }
181 }
182