| /* |
| * Copyright (C) 2020 The Dagger Authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dagger.hilt.android.internal.testing; |
| |
| import static dagger.hilt.internal.Preconditions.checkNotNull; |
| import static dagger.hilt.internal.Preconditions.checkState; |
| |
| import android.content.Context; |
| import androidx.test.core.app.ApplicationProvider; |
| import dagger.hilt.internal.GeneratedComponentManager; |
| import java.lang.annotation.Annotation; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.Description; |
| import org.junit.runners.model.Statement; |
| |
| /** |
| * A Junit {@code TestRule} that's installed in all Hilt tests. |
| * |
| * <p>This rule enforces that a Hilt TestRule has run. The Dagger component will not be created |
| * without this test rule. |
| */ |
| public final class MarkThatRulesRanRule implements TestRule { |
| private static final String HILT_ANDROID_APP = "dagger.hilt.android.HiltAndroidApp"; |
| private static final String HILT_ANDROID_TEST = "dagger.hilt.android.testing.HiltAndroidTest"; |
| |
| private final Context context = ApplicationProvider.getApplicationContext(); |
| private final Object testInstance; |
| private final boolean autoAddModule; |
| |
| private final AtomicBoolean started = new AtomicBoolean(false); |
| |
| public MarkThatRulesRanRule(Object testInstance) { |
| this.autoAddModule = true; |
| this.testInstance = checkNotNull(testInstance); |
| checkState( |
| hasAnnotation(testInstance, HILT_ANDROID_TEST), |
| "Expected %s to be annotated with @HiltAndroidTest.", |
| testInstance.getClass().getName()); |
| checkState( |
| context instanceof GeneratedComponentManager, |
| "Hilt test, %s, must use a Hilt test application but found %s. To fix, configure the test " |
| + "to use HiltTestApplication or a custom Hilt test application generated with " |
| + "@CustomTestApplication.", |
| testInstance.getClass().getName(), |
| context.getClass().getName()); |
| checkState( |
| !hasAnnotation(context, HILT_ANDROID_APP), |
| "Hilt test, %s, cannot use a @HiltAndroidApp application but found %s. To fix, configure " |
| + "the test to use HiltTestApplication or a custom Hilt test application generated " |
| + "with @CustomTestApplication.", |
| testInstance.getClass().getName(), |
| context.getClass().getName()); |
| } |
| |
| public void delayComponentReady() { |
| checkState(!started.get(), "Called delayComponentReady after test execution started"); |
| getTestApplicationComponentManager().delayComponentReady(); |
| } |
| |
| public void componentReady() { |
| checkState(started.get(), "Called componentReady before test execution started"); |
| getTestApplicationComponentManager().componentReady(); |
| } |
| |
| public void inject() { |
| getTestApplicationComponentManager().inject(); |
| } |
| |
| @Override |
| public Statement apply(final Statement base, Description description) { |
| started.set(true); |
| checkState( |
| description.getTestClass().isInstance(testInstance), |
| "HiltAndroidRule was constructed with an argument that was not an instance of the test" |
| + " class"); |
| return new Statement() { |
| @Override |
| public void evaluate() throws Throwable { |
| |
| TestApplicationComponentManager componentManager = getTestApplicationComponentManager(); |
| try { |
| // This check is required to check that state hasn't been set before this rule runs. This |
| // prevents cases like setting state in Application.onCreate for Gradle emulator tests |
| // that will get cleared after running the first test case. |
| componentManager.checkStateIsCleared(); |
| componentManager.setAutoAddModule(autoAddModule); |
| if (testInstance != null) { |
| componentManager.setTestInstance(testInstance); |
| } |
| componentManager.setHasHiltTestRule(description); |
| base.evaluate(); |
| componentManager.verifyDelayedComponentWasMadeReady(); |
| } finally { |
| componentManager.clearState(); |
| } |
| } |
| }; |
| } |
| |
| private TestApplicationComponentManager getTestApplicationComponentManager() { |
| checkState( |
| context instanceof TestApplicationComponentManagerHolder, |
| "The context is not an instance of TestApplicationComponentManagerHolder: %s", |
| context); |
| Object componentManager = ((TestApplicationComponentManagerHolder) context).componentManager(); |
| checkState( |
| componentManager instanceof TestApplicationComponentManager, |
| "Expected TestApplicationComponentManagerHolder to return an instance of" |
| + "TestApplicationComponentManager"); |
| return (TestApplicationComponentManager) componentManager; |
| } |
| |
| private static boolean hasAnnotation(Object obj, String annotationName) { |
| for (Annotation annotation : obj.getClass().getAnnotations()) { |
| if (annotation.annotationType().getName().contentEquals(annotationName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |