• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.internal.testing;
18 
19 import static dagger.hilt.internal.Preconditions.checkNotNull;
20 import static dagger.hilt.internal.Preconditions.checkState;
21 
22 import android.app.Application;
23 import androidx.test.core.app.ApplicationProvider;
24 import dagger.hilt.android.internal.Contexts;
25 import dagger.hilt.internal.GeneratedComponentManager;
26 import java.lang.annotation.Annotation;
27 import java.util.concurrent.atomic.AtomicBoolean;
28 import org.junit.rules.TestRule;
29 import org.junit.runner.Description;
30 import org.junit.runners.model.Statement;
31 
32 /**
33  * A Junit {@code TestRule} that's installed in all Hilt tests.
34  *
35  * <p>This rule enforces that a Hilt TestRule has run. The Dagger component will not be created
36  * without this test rule.
37  */
38 public final class MarkThatRulesRanRule implements TestRule {
39   private static final String HILT_ANDROID_APP = "dagger.hilt.android.HiltAndroidApp";
40   private static final String HILT_ANDROID_TEST = "dagger.hilt.android.testing.HiltAndroidTest";
41 
42   private final Application application = Contexts.getApplication(
43       ApplicationProvider.getApplicationContext());
44   private final Object testInstance;
45   private final boolean autoAddModule;
46 
47   private final AtomicBoolean started = new AtomicBoolean(false);
48 
MarkThatRulesRanRule(Object testInstance)49   public MarkThatRulesRanRule(Object testInstance) {
50     this.autoAddModule = true;
51     this.testInstance = checkNotNull(testInstance);
52     checkState(
53         hasAnnotation(testInstance, HILT_ANDROID_TEST),
54         "Expected %s to be annotated with @HiltAndroidTest.",
55         testInstance.getClass().getName());
56     checkState(
57         application instanceof GeneratedComponentManager,
58         "Hilt test, %s, must use a Hilt test application but found %s. To fix, configure the test "
59             + "to use HiltTestApplication or a custom Hilt test application generated with "
60             + "@CustomTestApplication.",
61         testInstance.getClass().getName(),
62         application.getClass().getName());
63     checkState(
64         !hasAnnotation(application, HILT_ANDROID_APP),
65         "Hilt test, %s, cannot use a @HiltAndroidApp application but found %s. To fix, configure "
66             + "the test to use HiltTestApplication or a custom Hilt test application generated "
67             +  "with @CustomTestApplication.",
68         testInstance.getClass().getName(),
69         application.getClass().getName());
70   }
71 
delayComponentReady()72   public void delayComponentReady() {
73     checkState(!started.get(), "Called delayComponentReady after test execution started");
74     getTestApplicationComponentManager().delayComponentReady();
75   }
76 
componentReady()77   public void componentReady() {
78     checkState(started.get(), "Called componentReady before test execution started");
79     getTestApplicationComponentManager().componentReady();
80   }
81 
inject()82   public void inject() {
83     getTestApplicationComponentManager().inject();
84   }
85 
86   @Override
apply(final Statement base, Description description)87   public Statement apply(final Statement base, Description description) {
88     started.set(true);
89     checkState(
90         description.getTestClass().isInstance(testInstance),
91         "HiltAndroidRule was constructed with an argument that was not an instance of the test"
92             + " class");
93     return new Statement() {
94       @Override
95       public void evaluate() throws Throwable {
96 
97         TestApplicationComponentManager componentManager = getTestApplicationComponentManager();
98         try {
99           // This check is required to check that state hasn't been set before this rule runs. This
100           // prevents cases like setting state in Application.onCreate for Gradle emulator tests
101           // that will get cleared after running the first test case.
102           componentManager.checkStateIsCleared();
103           componentManager.setAutoAddModule(autoAddModule);
104           if (testInstance != null) {
105             componentManager.setTestInstance(testInstance);
106           }
107           componentManager.setHasHiltTestRule(description);
108           base.evaluate();
109           componentManager.verifyDelayedComponentWasMadeReady();
110         } finally {
111           componentManager.clearState();
112         }
113       }
114     };
115   }
116 
117   private TestApplicationComponentManager getTestApplicationComponentManager() {
118     checkState(
119         application instanceof TestApplicationComponentManagerHolder,
120         "The application is not an instance of TestApplicationComponentManagerHolder: %s",
121         application);
122     Object componentManager =
123         ((TestApplicationComponentManagerHolder) application).componentManager();
124     checkState(
125         componentManager instanceof TestApplicationComponentManager,
126         "Expected TestApplicationComponentManagerHolder to return an instance of"
127             + "TestApplicationComponentManager");
128     return (TestApplicationComponentManager) componentManager;
129   }
130 
131   private static boolean hasAnnotation(Object obj, String annotationName) {
132     for (Annotation annotation : obj.getClass().getAnnotations()) {
133       if (annotation.annotationType().getName().contentEquals(annotationName)) {
134         return true;
135       }
136     }
137     return false;
138   }
139 }
140