Skip to content

Commit c40811e

Browse files
Chang-EricDagger Team
authored and
Dagger Team
committed
Add a SkipTestInjection annotation to disable injecting the test class. This may be useful for outside rules that wants to inject the test class from some other Hilt component.
This annotation may be used directly on the test class or on some other annotation, as typically outside rules would have some other code generators on their own to generate needed entry points. RELNOTES=Add @SkipTestInjection PiperOrigin-RevId: 608724405
1 parent 02d62d6 commit c40811e

File tree

9 files changed

+215
-2
lines changed

9 files changed

+215
-2
lines changed

java/dagger/hilt/android/testing/BUILD

+9
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ android_library(
168168
],
169169
)
170170

171+
android_library(
172+
name = "skip_test_injection",
173+
testonly = 1,
174+
srcs = ["SkipTestInjection.java"],
175+
deps = [
176+
":package_info",
177+
],
178+
)
179+
171180
java_library(
172181
name = "package_info",
173182
srcs = ["package-info.java"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (C) 2024 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.testing;
18+
19+
import static java.lang.annotation.RetentionPolicy.CLASS;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation used for skipping test injection in a Hilt Android Test. This may be useful if
27+
* building separate custom test infrastructure to inject the test class from another Hilt
28+
* component. This may be used on either the test class or an annotation that annotates
29+
* the test class.
30+
*/
31+
@Retention(CLASS)
32+
@Target({ElementType.TYPE})
33+
public @interface SkipTestInjection {}

java/dagger/hilt/processor/internal/ClassNames.java

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ public final class ClassNames {
170170
get("dagger.hilt.android.internal.testing", "TestInstanceHolder");
171171
public static final ClassName HILT_ANDROID_TEST =
172172
get("dagger.hilt.android.testing", "HiltAndroidTest");
173+
public static final ClassName SKIP_TEST_INJECTION =
174+
get("dagger.hilt.android.testing", "SkipTestInjection");
173175
public static final ClassName CUSTOM_TEST_APPLICATION =
174176
get("dagger.hilt.android.testing", "CustomTestApplication");
175177
public static final ClassName ON_COMPONENT_READY_RUNNER =

java/dagger/hilt/processor/internal/root/RootProcessingStep.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ public void processEach(ClassName annotation, XElement element) throws Exception
8484
// for unrelated changes in Gradle.
8585
RootType rootType = RootType.of(rootElement);
8686
if (rootType.isTestRoot()) {
87-
new TestInjectorGenerator(processingEnv(), TestRootMetadata.of(processingEnv(), rootElement))
88-
.generate();
87+
TestRootMetadata testRootMetadata = TestRootMetadata.of(processingEnv(), rootElement);
88+
if (testRootMetadata.skipTestInjectionAnnotation().isEmpty()) {
89+
new TestInjectorGenerator(processingEnv(), testRootMetadata).generate();
90+
}
8991
}
9092

9193
XTypeElement originatingRootElement =

java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static javax.lang.model.element.Modifier.STATIC;
2626

2727
import androidx.room.compiler.processing.JavaPoetExtKt;
28+
import androidx.room.compiler.processing.XAnnotation;
2829
import androidx.room.compiler.processing.XConstructorElement;
2930
import androidx.room.compiler.processing.XFiler.Mode;
3031
import androidx.room.compiler.processing.XProcessingEnv;
@@ -41,6 +42,7 @@
4142
import dagger.hilt.processor.internal.Processors;
4243
import java.io.IOException;
4344
import java.util.List;
45+
import java.util.Optional;
4446

4547
/** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentData}. */
4648
public final class TestComponentDataGenerator {
@@ -222,6 +224,13 @@ private MethodSpec getTestInjectInternalMethod() {
222224
}
223225

224226
private CodeBlock callInjectTest(XTypeElement testElement) {
227+
Optional<XAnnotation> skipTestInjection =
228+
rootMetadata.testRootMetadata().skipTestInjectionAnnotation();
229+
if (skipTestInjection.isPresent()) {
230+
return CodeBlock.of(
231+
"throw new IllegalStateException(\"Cannot inject test when using @$L\")",
232+
skipTestInjection.get().getName());
233+
}
225234
return CodeBlock.of(
226235
"(($T) (($T) $T.getApplication($T.getApplicationContext()))"
227236
+ ".generatedComponent()).injectTest(testInstance)",

java/dagger/hilt/processor/internal/root/TestRootMetadata.java

+25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package dagger.hilt.processor.internal.root;
1818

19+
import androidx.room.compiler.processing.XAnnotation;
1920
import androidx.room.compiler.processing.XElement;
2021
import androidx.room.compiler.processing.XProcessingEnv;
2122
import androidx.room.compiler.processing.XTypeElement;
@@ -25,6 +26,8 @@
2526
import dagger.hilt.processor.internal.ProcessorErrors;
2627
import dagger.hilt.processor.internal.Processors;
2728
import dagger.internal.codegen.xprocessing.XElements;
29+
import java.util.Optional;
30+
import java.util.Set;
2831
import javax.lang.model.element.TypeElement;
2932

3033
/** Metadata class for {@code InternalTestRoot} annotated classes. */
@@ -57,6 +60,28 @@ ClassName testInjectorName() {
5760
return Processors.append(Processors.getEnclosedClassName(testName()), "_GeneratedInjector");
5861
}
5962

63+
/**
64+
* Returns either the SkipTestInjection annotation or the first annotation that was annotated
65+
* with SkipTestInjection, if present.
66+
*/
67+
Optional<XAnnotation> skipTestInjectionAnnotation() {
68+
XAnnotation skipTestAnnotation = testElement().getAnnotation(ClassNames.SKIP_TEST_INJECTION);
69+
if (skipTestAnnotation != null) {
70+
return Optional.of(skipTestAnnotation);
71+
}
72+
73+
Set<XAnnotation> annotatedAnnotations = testElement().getAnnotationsAnnotatedWith(
74+
ClassNames.SKIP_TEST_INJECTION);
75+
if (!annotatedAnnotations.isEmpty()) {
76+
// Just return the first annotation that skips test injection if there are multiple since
77+
// at this point it doesn't really matter and the specific annotation is only really useful
78+
// for communicating back to the user.
79+
return Optional.of(annotatedAnnotations.iterator().next());
80+
}
81+
82+
return Optional.empty();
83+
}
84+
6085
static TestRootMetadata of(XProcessingEnv env, XElement element) {
6186

6287
XTypeElement testElement = XElements.asTypeElement(element);

javatests/dagger/hilt/android/testing/BUILD

+34
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,37 @@ android_local_test(
158158
"//third_party/java/truth",
159159
],
160160
)
161+
162+
android_local_test(
163+
name = "SkipTestInjectionTest",
164+
srcs = ["SkipTestInjectionTest.java"],
165+
manifest_values = {
166+
"minSdkVersion": "15",
167+
"targetSdkVersion": "27",
168+
},
169+
deps = [
170+
"//:android_local_test_exports",
171+
"//:dagger_with_compiler",
172+
"//java/dagger/hilt/android/testing:hilt_android_test",
173+
"//java/dagger/hilt/android/testing:skip_test_injection",
174+
"//third_party/java/jsr330_inject",
175+
"//third_party/java/truth",
176+
],
177+
)
178+
179+
android_local_test(
180+
name = "SkipTestInjectionAnnotationTest",
181+
srcs = ["SkipTestInjectionAnnotationTest.java"],
182+
manifest_values = {
183+
"minSdkVersion": "15",
184+
"targetSdkVersion": "27",
185+
},
186+
deps = [
187+
"//:android_local_test_exports",
188+
"//:dagger_with_compiler",
189+
"//java/dagger/hilt/android/testing:hilt_android_test",
190+
"//java/dagger/hilt/android/testing:skip_test_injection",
191+
"//third_party/java/jsr330_inject",
192+
"//third_party/java/truth",
193+
],
194+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (C) 2024 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.testing;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertThrows;
21+
22+
import androidx.test.ext.junit.runners.AndroidJUnit4;
23+
import javax.inject.Inject;
24+
import org.junit.Rule;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.robolectric.annotation.Config;
28+
29+
@HiltAndroidTest
30+
@SkipTestInjectionAnnotationTest.TestAnnotation
31+
@RunWith(AndroidJUnit4.class)
32+
@Config(application = HiltTestApplication.class)
33+
public final class SkipTestInjectionAnnotationTest {
34+
@Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
35+
36+
@SkipTestInjection
37+
@interface TestAnnotation {}
38+
39+
@Inject String string; // Never provided, shouldn't compile without @SkipTestInjection
40+
41+
@Test
42+
public void testCannotCallInjectOnTestRule() throws Exception {
43+
IllegalStateException exception =
44+
assertThrows(
45+
IllegalStateException.class,
46+
() -> rule.inject());
47+
assertThat(exception)
48+
.hasMessageThat()
49+
.isEqualTo("Cannot inject test when using @TestAnnotation");
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (C) 2024 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.testing;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertThrows;
21+
22+
import androidx.test.ext.junit.runners.AndroidJUnit4;
23+
import javax.inject.Inject;
24+
import org.junit.Rule;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.robolectric.annotation.Config;
28+
29+
@HiltAndroidTest
30+
@SkipTestInjection
31+
@RunWith(AndroidJUnit4.class)
32+
@Config(application = HiltTestApplication.class)
33+
public final class SkipTestInjectionTest {
34+
@Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
35+
36+
@Inject String string; // Never provided, shouldn't compile without @SkipTestInjection
37+
38+
@Test
39+
public void testCannotCallInjectOnTestRule() throws Exception {
40+
IllegalStateException exception =
41+
assertThrows(
42+
IllegalStateException.class,
43+
() -> rule.inject());
44+
assertThat(exception)
45+
.hasMessageThat()
46+
.isEqualTo("Cannot inject test when using @SkipTestInjection");
47+
}
48+
}

0 commit comments

Comments
 (0)