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