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