blob: 17c40b308ddceb7bd99a18411662875dc0cf6777 [file] [log] [blame]
/*
* 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;
}
}