원클릭으로
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.