Skip to content

Add a recipe for testing composables that use Hilt - #321

Open
DanielJette wants to merge 2 commits into
mainfrom
docs/207-hilt
Open

DanielJette wants to merge 2 commits into
mainfrom
docs/207-hilt

Conversation

@DanielJette

@DanielJette DanielJette commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Important

Merge after #317 and #334. This page links to recipe 25, which is still in #317, and to three
files in Samples/Flix/FlixHilt, which #334 adds. Both are relative/blob/main links on purpose —
the alternative was absolute URLs that pass the link checker while being broken on the live site.

What does this change accomplish?

Composables that use hiltViewModel() fail under ComposableTestActivity (#207), and hilt returns no results in docs search.

Fixes #207

How have you achieved it?

  • New recipe recipes/27-hilt.md: Hilt testing dependencies, a HiltTestApplication runner, an @AndroidEntryPoint subclass of ComposableTestActivity in the debug source set and its manifest, and a test using HiltAndroidRule with ComposableScreenshotScenarioRule.
  • The host activity needs testify-compose and testify on the debug compile classpath. The recipe uses debugCompileOnly: testify-compose publishes testify as a runtime-only dependency, so both are needed for the TestifyResourcesOverride supertype, and debugImplementation pulls Testify's test dependencies into the app, which broke dependency resolution in Flix (androidx.test.ext:junit 1.1.5 vs 1.3.0).
  • Notes that ComposableScreenshotRule always launches ComposableTestActivity, so it can't be used with Hilt.

Scope of Impact and Testing instructions

Documentation only; no library or plugin changes, so no CHANGELOG entry. Built locally with yarn build from docs/. No broken links. The setup was run in the Flix sample (Hilt 2.60.1, KSP) on an API 37 emulator: a composable calling hiltViewModel<NavigationViewModel>() recorded successfully with the Hilt host activity, and the same test with ComposableTestActivity failed with the #207 exception. After moving the host activity to src/debug with the debugCompileOnly dependencies, the same test recorded with screenshotRecord and then passed with screenshotTest. That test code was not committed.

Each commit includes [skip ci] so CI doesn't run for this docs-only change.

Why this was reopened

Closed on 2026-09-25 after approval, pending an executable example — tracked as #325. #334 now
provides it, and the worked-example section at the bottom of the page points at it step by step.

Every claim in steps 1–4 still holds against main:

  • ComposableScreenshotRule still hardcodes activityClass = ComposableTestActivity::class.java
    (Ext/Compose/.../ComposableScreenshotRule.kt:54), so the note about it remains correct.
  • ComposableTestActivity is still open class ... : AppCompatActivity(), TestifyResourcesOverride,
    which is why both debugCompileOnly artifacts are needed.
  • The plugin still reads android.defaultConfig.testInstrumentationRunner
    (Plugins/Gradle/.../TestifyExtension.kt:119), so step 2's claim about screenshotTest holds.

Rebased onto current main. The worked-example section was reviewed separately and rewritten: the
sample in #334 now uses src/debug and debugCompileOnly, so it matches these steps exactly rather
than departing from them.

Notice

Warning

This change must keep main in a shippable state; it may be shipped without further notice.

@AndroidTestifyBot AndroidTestifyBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested

Two fixes, plus a standing caveat about verification.

1. Step 3 contradicts itself — use the debug source set

In your androidTest sources, create a subclass of ComposableTestActivity […]
Declare it in the manifest of your debug source set, src/debug/AndroidManifest.xml

Those are different APKs — an androidTest class compiles into the test APK, while src/debug/AndroidManifest.xml merges into the app APK's manifest.

The src/debug/AndroidManifest.xml instruction is the correct half and matches the house convention (extensions/compose/1-setup.md and Samples/Flix). Please change step 3 to put HiltComposableTestActivity in the debug source set, not androidTest.

2. Drop the kapt fallback

If your project uses kapt instead of KSP, use kaptAndroidTest.

AGP 9 removed kapt, so this is a trap rather than a fallback. Please remove the sentence. The <hilt version> placeholders are fine as-is — better than pinning a version that will go stale.

Verified correct

  • ComposableTestActivity is open, so the subclass compiles. Worth stating explicitly because the whole recipe depends on it.
  • ComposableScreenshotRule genuinely can't be used with Hilt — it hardcodes activityClass = ComposableTestActivity::class.java in its superclass call. The :::note steering readers to ComposableScreenshotScenarioRule is correct and well-reasoned.
  • setCompose { }, withScenario(scenario), @Rule(order = …) and the HiltTestRunner snippet are all right.
  • "The Testify Gradle Plugin reads this setting" — true, TestifySettings.create falls back to android.defaultConfig.testInstrumentationRunner.
  • @style/Theme.AppCompat.NoActionBar matches what 1-setup.md and Samples/Flix already use for ComposableTestActivity, so the Hilt host is visually consistent and existing baselines shouldn't move.
  • The error string is already introduced with "an error like this", which is the right hedge for a message we can't reproduce in-repo.

Standing caveat

This is the least-verified page in the batch: there is no Hilt screenshot test anywhere in this repo, so the end-to-end sequence has never been run here. The individual claims check out; the sequence has no working example behind it. Unlike #318, which lifts its examples from FullscreenCaptureExampleTests.kt, nothing here compiles in CI. A minimal Hilt sample would convert this from plausible to proven — worth considering as a follow-up.

Reviewed by Claude on behalf of @DanielJette.

@AndroidTestifyBot AndroidTestifyBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

Both requested changes are in, and the source-set move surfaced something I hadn't thought through.

The contradiction is resolved

Step 3 now says "In your debug source set, src/debug/java", matching the manifest it tells you to edit and the house convention in extensions/compose/1-setup.md and Samples/Flix. The kapt sentence is gone. <hilt version> placeholders kept.

The new debugCompileOnly section is correct

This is the part I'd have missed, and it follows directly from moving the activity out of androidTest:

Code in the debug source set can't see your androidTest dependencies […]
Both are needed, because ComposableTestActivity implements an interface from the core testify library.

Verified against the published coordinates. ComposableTestActivity is in Ext/Compose, published as dev.testify:testify-compose (artifact: 'testify-compose'), and it's declared open class ComposableTestActivity : AppCompatActivity(), TestifyResourcesOverride — where TestifyResourcesOverride lives in Library/src/main/java/dev/testify/resources/, published as dev.testify:testify (artifact: 'testify'). So subclassing it genuinely does need both artifacts on the compile classpath. Precisely argued.

compileOnly being sufficient is the right call too: the classes come from the test APK at runtime, exactly as they already do for ComposableTestActivity under the existing documented setup.

Re-verified

  • ComposableTestActivity is open, so the subclass compiles.
  • ComposableScreenshotRule hardcodes activityClass = ComposableTestActivity::class.java, so the note steering readers to the scenario rule is correct.
  • @style/Theme.AppCompat.NoActionBar matches what 1-setup.md and Samples/Flix already use, so baselines shouldn't move.
  • The error string is introduced with "an error like this" — the right hedge.

Docs build verified locally: exit 0, zero warnings, individually and combined.

Standing caveat, unchanged

There's still no Hilt screenshot test in the repo, so the end-to-end sequence has never been run here. Every individual claim checks out and the reasoning is sound — but this remains the one page in the batch with no executable example behind it. A minimal Hilt sample would close that gap. Approving on the strength of the reasoning and the verified parts; flagging it so it's a known, deliberate risk rather than an unexamined one.

Reviewed by Claude on behalf of @DanielJette.

@AndroidTestifyBot

Copy link
Copy Markdown
Contributor

Filed #325 to track the follow-up noted in the approval: a Hilt screenshot test in the sample suite, so this recipe has an executable example behind it the way #318 has FullscreenCaptureExampleTests.kt.

Samples/Flix looks like the cheapest home — the Hilt plugin, hilt-navigation-compose, @AndroidEntryPoint on MainActivity, and a Compose screenshot suite are all already there.

The issue lists the six steps from this page that are currently unverified, including the debugCompileOnly pair and the GeneratedComponent error string. Not blocking this PR.

@DanielJette

Copy link
Copy Markdown
Contributor Author

Added the worked-example section now that #334 provides the compiled sample this was waiting on — that PR adds Samples/Flix/FlixHilt, and the recipe links to its three files step by step.

It also records the two ways the sample departs from the steps here, since they would otherwise read as contradictions:

  • The sample puts the host activity in main with compileOnly, because that module exists only to hold tests. The debug source set guidance here remains right for an application module, which is what the recipe is about.
  • testInstrumentationRunner is per module, so the sample is its own module. Adding it to :FlixSample instead replaced FlixApplication — and with it the Coil ImageLoader it supplies via ImageLoaderFactory — which broke CastMemberScreenshotTest and MoviePosterScreenshotTest. Worth warning about, since it is not obvious until the images come back blank.

Rebased onto current main. Verified with npm run build; the first attempt at that cross-link was a relative path to recipe 25, which onBrokenLinks: throw correctly rejected because that page is still in #317 — it is now an absolute URL.

@AndroidTestifyBot AndroidTestifyBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested

The earlier approval was for the page without the worked-example section. That section was added afterwards, so this replaces it. Steps 1–4 are unchanged and still check out; the docs build is clean. The new section is the problem.

1. Three links are dead until #334 merges

The worked example links to blob/main/Samples/Flix/FlixHilt/.... Those files exist only on #334's branch. If this merges first, all three 404. Either merge strictly after #334 or hold the section back until then. The PR description still says "the cross-link to the sample test will land with #325 so this page has no dependency on it", which is no longer what the diff does.

2. The link to the blank-screenshots recipe sidesteps the link checker

Per your own comment, the relative link to recipe 25 was rejected by onBrokenLinks: throw because that page is still in #317, so it became https://testify.dev/docs/recipes/blank-or-incomplete-screenshots. That makes the build pass and the link broken on the live site until #317 ships. Use the relative link and merge after #317, or drop the sentence.

3. "The FlixHilt sample module is this recipe, compiled and running in CI"

It isn't this recipe. Step 3 here is the debug source set, debugCompileOnly, and src/debug/AndroidManifest.xml; the sample uses main and compileOnly. The section goes on to say so, but the opening sentence claims more than the sample delivers, and steps 1–4 of #325's checklist remain unverified by anything compiled. I've asked on #334 for the sample to move to src/debug + debugCompileOnly; if that happens this sentence becomes true and the first "does differently" bullet can go.

Also on #334: the sample's activity works equally well from src/androidTest with an androidTest manifest and no compileOnly at all (I ran it; same baseline). So "there is no shipping build to keep the activity out of" is a reason for the choice, not the only arrangement that works.

4. "running in CI"

Only on Bitrise test_flix, once #334 lands. flix_sample.yml doesn't build FlixHilt.

Housekeeping

The description needs a refresh: "the diff is still the single new file" and the no-dependency sentence predate the worked-example commit.

@DanielJette

Copy link
Copy Markdown
Contributor Author

Agreed on all four. The section is rewritten and the description corrected.

1 and 3 — the sample now is this recipe

Rather than keep qualifying the claim, I changed the sample. #334 moves HiltComposableTestActivity to src/debug/java, its declaration to src/debug/AndroidManifest.xml, and both Testify artifacts to debugCompileOnly. So steps 1–4 of #325's checklist are now backed by something compiled, the opening sentence is true, and the main/compileOnly bullet is gone.

Your src/androidTest finding is recorded where it belongs — in the activity's KDoc — rather than in the recipe, which is about application modules. It now says debug is used to match the recipe and that androidTest works equally well in a library module.

The three file links are updated to the new paths. They still require #334 to merge first, which is now noted in the description.

2 — relative link restored, and my reasoning for dropping it was wrong

Switched back to […](25-blank-or-incomplete-screenshots.md). You were right that the absolute URL just moved the breakage somewhere the checker could not see it.

Worth recording why I had reached for it: the relative link does resolve, and my earlier conclusion that onBrokenLinks: throw rejected it was a stale .docusaurus cache. With recipe 25 from #317 dropped in and npm run clear first, the build is clean:

$ git show docs/91-93-blank-screenshots:docs/docs/recipes/25-blank-or-incomplete-screenshots.md \
    > docs/docs/recipes/25-blank-or-incomplete-screenshots.md
$ npm run clear && npm run build
[SUCCESS] Generated static files in "build".

So the link is correct and this branch's docs build fails standalone purely because recipe 25 is still in #317 — the merge-order dependency you identified, not a bad link.

4 — "running in CI"

Corrected to just "runs on CI", and #334's description now states that Bitrise test_flix is the only coverage; flix_sample.yml's path filter triggers the workflow but its steps never build FlixHilt.

Merge order

#317 → #334 → #321. #317 for the relative link, #334 for the three file links. Noted at the top of the description so it does not get lost.

@AndroidTestifyBot AndroidTestifyBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — merge after #317 and #334

All four points are resolved.

  • With #334 now putting the activity in src/debug with debugCompileOnly, "follows these steps exactly" is true, and the "does differently" bullet is rightly gone.
  • The link to recipe 25 is relative again, so onBrokenLinks: throw guards it. I checked your stale-cache explanation: this branch alone fails the docs build on that one link, and with #317's recipe 25 dropped in and npm run clear first, it builds clean. The only problem is merge order.
  • "runs on CI" is accurate once #334 lands (Bitrise test_flix).
  • The :::caution about testInstrumentationRunner being per module is a better home for the Flix finding than the old bullet.

Merge order: #317 → #334 → #321, as the description now says. Merged earlier, this breaks the docs build (recipe 25 link) and leaves four 404ing blob/main/Samples/Flix/FlixHilt/... links.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IllegalStateException: Given component holder class ComposableTestActivity does not implement interface GeneratedComponent

2 participants