ワンクリックで
add-tests
Add missing test cases, targeting a specific file or all changed files on the branch
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Add missing test cases, targeting a specific file or all changed files on the branch
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Quick review of the current diff for bugs, logic errors, convention violations, and test gaps
Scaffold a new feature module with the appropriate layers and MESA conventions
Scaffold a new Strata interactor with interface, implementation, fake, and test
Add a new screen to an existing feature module with StateHolder, UI, factories, and tests
Bump library versions in gradle.properties and prepare release notes
Diagnose and fix an error from a build failure, stack trace, or error message
| name | add-tests |
| description | Add missing test cases, targeting a specific file or all changed files on the branch |
| disable-model-invocation | true |
| argument-hint | [@<filepath>] [--unit] [--ui] [--both] |
Add test coverage for a specific file or for all testable files changed on the branch. If test files already exist, analyze them for missing test cases and add them. If no test files exist, create them from scratch.
Input: $ARGUMENTS
If a file path is provided: Use that file as the single target.
If no file path is provided: Run git diff main --name-only to get all changed files on the branch. Filter to testable source files (StateHolders, UI Composables, Interactors, UseCases, Repositories). Exclude test files, build files, and configuration. Process each testable file.
For each target file, read it and understand:
features/counter/presentation)Determine which test types apply:
--unit → JVM unit tests only--ui → Android instrumented UI tests only--both → Both| Component | Test Type | Location |
|---|---|---|
| StateHolder | Unit (JVM) | src/test/ |
| UI Composable | UI (androidTest) | src/androidTest/ |
| Interactor / UseCase | Unit (JVM) | src/test/ |
| Repository | Unit (JVM) | src/test/ |
Search for existing test files:
{Name}StateHolder.kt → look for {Name}StateHolderTest.kt in src/test/{Name}Ui.kt → look for {Name}UiTest.kt in src/androidTest/, plus robot/{Name}UiRobot.kt and testdata/{Name}UiTestData.kt{Name}Interactor.kt / {Name}UseCase.kt → look for {Name}Test.kt in src/test/{Name}Repository.kt → look for {Name}Test.kt in src/test/Also check for existing Fake files in the fakes/ subpackage.
If test files exist: Read them and compare against the implementation to identify:
If test files do not exist: Create them from scratch following the patterns below.
src/test/)Mirror the implementation package structure. Place fakes in a fakes/ subpackage:
src/test/java/<package>/
├── {Name}Test.kt
└── fakes/
└── Fake{Dependency}.kt
src/androidTest/)Mirror the implementation package structure. Use robot/ and testdata/ subdirectories:
src/androidTest/java/<package>/
├── {Name}Test.kt
├── robot/
│ └── {Name}Robot.kt
└── testdata/
└── {Name}TestData.kt
{SubjectName}Test.kt (e.g., CounterStateHolderTest.kt, CounterUiTest.kt){SubjectName}Robot.kt (e.g., CounterUiRobot.kt){SubjectName}TestData.kt (e.g., CounterUiTestData.kt)Fake{DependencyName}.kt (e.g., FakeAppInterop.kt)BehaviorSpec with coroutineTestScope = trueshouldBe, shouldBeInstanceOf, shouldBeNull, shouldNotBeNull, shouldContain, etc.)TrapezeStateHolder.test {} extension for StateHolder testsFakeTrapezeNavigator from trapeze-test libraryTestEventSink from trapeze-test when testing event recordingio.mockk:mockk) only when faking would be impractical (e.g., complex interfaces with many methods, Android framework classes, or third-party library types)StateHolder tests use the trapeze-test library's TrapezeStateHolder.test {} extension which runs produceState in a headless Compose runtime via Molecule + Turbine. StateHolder tests are always JVM unit tests, never instrumented tests.
class {Name}StateHolderTest : BehaviorSpec({
Given("a {Name}StateHolder with {initial condition}") {
When("{action or event}") {
Then("{expected outcome}") {
val navigator = FakeTrapezeNavigator()
val holder = {Name}StateHolder(/* assisted params, fakes, navigator */)
holder.test {
val state = awaitItem()
// assert initial state
state.eventSink({Name}Event.SomeEvent)
awaitItem().someProperty shouldBe expectedValue
}
}
}
}
})
class {Name}Test : BehaviorSpec({
Given("a {Name} that succeeds") {
When("invoked with params") {
Then("it returns Success with the result") {
val subject = {Name}Impl(/* faked dependencies */)
val result = subject(params)
result.shouldBeInstanceOf<StrataResult.Success<*>>()
result.data shouldBe expectedValue
}
}
}
Given("a {Name} that fails") {
When("invoked") {
Then("it returns Failure") {
// setup fake to fail, invoke, assert Failure
}
}
}
})
When dependencies need faking, create Fake classes in a fakes/ subpackage:
shouldFail: Boolean constructor param, MutableStateFlow for observables, mutableListOf to record calls)src/test/ and src/androidTest/, create it in both source sets under their respective fakes/ subpackages (they do not share code)createComposeRule()io.mockk:mockk-android variant) only when faking is impracticaltestdata/{Name}TestData.kt)Provides pre-built State objects for tests. Each factory method accepts an eventSink parameter defaulting to {}:
object {Name}UiTestData {
fun defaultState(
eventSink: ({Name}Event) -> Unit = {}
) = {Name}State(
// sensible default property values
eventSink = eventSink
)
fun stateWith{Variation}(
// override specific properties
eventSink: ({Name}Event) -> Unit = {}
) = {Name}State(
// variation-specific values
eventSink = eventSink
)
}
robot/{Name}Robot.kt)Encapsulates UI interactions and assertions. Methods return this for chaining:
class {Name}UiRobot(private val composeTestRule: ComposeContentTestRule) {
fun setContent(state: {Name}State) = apply {
composeTestRule.setContent {
{Name}Ui(state = state)
}
}
// --- Assertions ---
fun assertTextDisplayed(text: String) = apply {
composeTestRule.onNodeWithText(text).assertExists()
}
fun assertTextNotDisplayed(text: String) = apply {
composeTestRule.onNodeWithText(text).assertDoesNotExist()
}
// --- Actions ---
fun clickButton(text: String) = apply {
composeTestRule.onNodeWithText(text).performClick()
}
}
{Name}UiTest.kt)Uses the robot and test data. Test method names follow the pattern givenX_whenY_thenZ:
class {Name}UiTest {
@get:Rule
val composeTestRule = createComposeRule()
private val robot get() = {Name}UiRobot(composeTestRule)
@Test
fun givenA{Name}State_whenDisplayed_thenItShows{ExpectedContent}() {
robot
.setContent({Name}UiTestData.defaultState())
.assertTextDisplayed("expected text")
}
@Test
fun givenA{Name}State_when{Button}IsClicked_then{Event}IsEmitted() {
var event: {Name}Event? = null
robot
.setContent({Name}UiTestData.defaultState(eventSink = { event = it }))
.clickButton("Button Text")
event shouldBe {Name}Event.SomeEvent
}
}
When a test file exists but is missing the robot pattern structure:
robot/ and testdata/ filesAll generated files MUST include the Apache 2.0 license header:
/*
* Copyright 2026 Jason Jamieson
*
* 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.
*/
After generating or updating tests, attempt to compile:
./gradlew :<module>:testDebugUnitTest./gradlew :<module>:connectedAndroidTestFix any compilation issues before finishing.