AGENT SKILL

mobiai-write-tests

Use when the user asks to write tests for mobile code, or after applying a fix that needs regression tests.

适用职业开发工程师
使用场景前端开发
License 许可
MIT
最近更新
2026-11-13
Stars
★ 492
Forks
14

01 前言 Front Matter

name
mobiai-write-tests
license
MIT
platforms
[ "android", "ios", "kmp", "flutter", "react-native" ]
description
Use when the user asks to write tests for mobile code, or after applying a fix that needs regression tests.
compatibility
[ "claude-code", "cursor", "copilot", "codex" ]

03 SKILL.md 正文

Write Tests

Write tests that cover a bug fix or new feature, following the project's existing patterns and platform conventions.

When to Use

  • After applying a fix, to prevent regression
  • User asks to add tests for specific code
  • As part of the mobiai-fix-issue pipeline

Note: If you're implementing a new feature or fix from scratch, consider using mobiai-mobile-tdd instead — it enforces writing tests FIRST (Red-Green-Refactor). This skill is for adding tests to existing code.

Pre-flight: check the Brain for relevant patterns

Standalone invocations only — skip this section when called from mobiai-fix-issue or mobiai-mobile-tdd, which run their own pre-flight or wrap this skill mid-flow.

If <repo>/.mobiai/brain/config.json exists, do a quick review pass before designing the test cases. A known workaround in the same area might shape what's worth asserting (the test should verify the workaround still works, not assume the fix is upstream).

Preferred — MCP tool (when mobiai-brain MCP server is registered):

Invoke mobile_review with default args. Focus on entries whose platform matches the project's platform and whose area overlaps with the system under test (datastore, networking, lifecycle, etc.). Run mobile_scan first if you don't already know the project's platform.

Fallback — CLI:

mobiai brain review --no-fail

If 1+ overdue entries match, mention them to the user before Step 1:

"Heads up — there are N overdue workaround(s) in <platform>/<area> that may shape what these tests need to cover: <title>. I'll proceed; let me know if you want to look at those first."

Then continue with Step 1. Skip silently when:

  • The brain isn't initialized.
  • No overdue entries match the area / platform of the code under test.

Pre-flight: check the Graph for code structure

Standalone invocations only — skip this section when called from mobiai-fix-issue or mobiai-mobile-tdd, which run their own Graph pre-flight or wrap this skill mid-flow.

The Graph helps locate the system under test, its callers, and nearby existing tests — useful before Step 1 (Understand What to Test) and Step 2 (Find Existing Test Patterns).

If <repo>/.mobiai/graph/index.json exists:

# Freshness — if "hace Xd" with X ≥ 1, suggest refresh (don't auto-run):
mobiai graph status

# Locate the symbol being tested:
mobiai graph search <ClassOrFunctionUnderTest>

# See how it's used in the codebase (informs which test cases matter):
mobiai graph callers <ClassOrFunctionUnderTest>

# Find existing test classes nearby (informs patterns and naming):
mobiai graph search Test --kind class --limit 20

The callers list reveals which call sites depend on the behavior — those are the cases worth covering. The existing-test search shows the project's actual test conventions (framework, naming, location).

If the index is ≥1 day old:

"El índice del Graph es de hace Xd. Si querés resultados frescos, corré mobiai graph init."

Don't run init autonomously.

If .mobiai/graph/index.json does not exist and the project has .kt or .swift files:

"No veo el índice del Graph. Generalo con mobiai graph init y arranco — sin él tengo que descubrir las convenciones de tests grepeando."

Don't run init autonomously.

Skip silently when:

  • The project has no Kotlin/Swift files (Flutter or RN-only).
  • You're testing pure Dart / JS / TS code (Graph doesn't index those in V1).

Workflow

Step 1: Understand What to Test

  1. Read the changed files to understand what was fixed or added
  2. Identify the testable unit — the method, class, or behavior that changed
  3. Define test cases:
    • The specific behavior that was broken and is now fixed
    • Edge cases related to the change
    • Boundary conditions (null inputs, empty collections, max values)

Step 2: Find Existing Test Patterns

Before writing tests, find and read existing tests in the project:

Search for existing test files near the changed code:

  • Use the Glob tool with patterns like **/test/**/*Test* or **/*_test.*
  • Or use Grep to search for test classes: class.*Test in test directories

Read existing tests to understand:

  • Which test framework is used
  • Naming conventions
  • Mocking approach
  • How dependencies are set up
  • Where test files are located relative to source files

Step 3: Write Tests

Follow the project's existing patterns. If no patterns exist, follow platform defaults below.

Step 4: Run Tests

Run the test suite to verify:

  • Your new tests pass
  • Existing tests still pass (no regressions from the fix)

If tests fail, fix them. If existing tests fail and your fix caused the regression, fix that too.

Step 5: Capture in MobiAI Brain (if worth remembering)

Once tests pass, check whether <repo>/.mobiai/brain/config.json exists. If it does NOT, skip this step silently — not every project uses Brain.

If it does, decide whether this test is a reusable pattern worth saving as a testing_pattern entry. A test is worth saving when it captures a non-obvious mobile-specific gotcha — async/lifecycle timing, platform quirks, flaky-test workarounds, fixture patterns. Skip when the test is a routine assertion on business logic.

When it's worth saving, propose the save to the user first (one-line confirmation). Never invoke silently.

Preferred — MCP tool (if your client has the mobiai-brain MCP server registered, you'll see this tool in your toolbox):

Invoke mobile_save_testing with:

  • title: short pattern name, e.g. "DataStore clear waits for empty emission"
  • platform: android | ios | shared | kmp | flutter | react-native
  • area: free-form, e.g. "datastore" | "coroutines" | "xctest" | "maestro"
  • files: array of repo-relative paths (test + system-under-test)
  • body: Markdown with ### Problem / ### Solution / ### Example (omit ### Example when the language doesn't fit)

Fallback — CLI (if MCP isn't configured):

mobiai brain save testing \
  --title "<short pattern name, e.g. 'DataStore clear waits for empty emission'>" \
  --platform <android|ios|shared|kmp|flutter|react-native> \
  --area <free-form, e.g. datastore | coroutines | xctest | maestro> \
  --files "<test_file>,<sut_file>" \
  --body "### Problem
What goes wrong without this pattern (race, leak, false pass, etc.).

### Solution
The pattern itself, in one paragraph.

### Example
\`\`\`kotlin
// minimal code snippet
\`\`\`"

Skip the body's ### Example block on languages that don't fit. The fewer placeholders, the better — concrete pattern in the user's own words beats a generic template.

Platform Conventions

Android (Kotlin)

Default framework: JUnit 4 + MockK

Test location: Mirror source path under test directory

  • Source: app/src/main/java/com/example/feature/MyClass.kt
  • Test: app/src/test/java/com/example/feature/MyClassTest.kt

Naming: Backtick-enclosed descriptive names

@Test
fun `calculateTotal returns zero when cart is empty`() { ... }

@Test
fun `formatPrice throws when amount is negative`() { ... }

Mocking: Use io.mockk (mockk, every, verify, coEvery for coroutines)

Coroutines: Use kotlinx-coroutines-test with runTest

Run tests:

./gradlew test<Flavor>DebugUnitTest --no-daemon

Rules:

  • No Android framework in unit tests — use Robolectric only if absolutely needed
  • Mock external dependencies (repositories, APIs, DAOs)
  • Test ViewModels with MainDispatcherRule or Dispatchers.setMain()

iOS (Swift)

Default framework: XCTest

Test location: Inside the test target

  • Source: MyApp/Feature/MyClass.swift
  • Test: MyAppTests/Feature/MyClassTests.swift

Naming: test prefix + descriptive name

func testCalculateTotal_returnsZero_whenCartIsEmpty() { ... }
func testFormatPrice_throws_whenAmountIsNegative() { ... }

Run tests:

xcodebuild test -scheme <scheme> -destination 'platform=iOS Simulator,name=iPhone 15' -quiet

Flutter (Dart)

Default framework: flutter_test

Test location: test/ directory mirroring lib/

  • Source: lib/feature/my_class.dart
  • Test: test/feature/my_class_test.dart

Naming: test() with descriptive strings

test('calculateTotal returns zero when cart is empty', () { ... });
group('formatPrice', () {
  test('throws when amount is negative', () { ... });
});

Run tests:

flutter test

React Native (TypeScript/JavaScript)

Default framework: Jest

Test location: __tests__/ directory or co-located .test.ts files

Naming: describe + it blocks

describe('calculateTotal', () => {
  it('returns zero when cart is empty', () => { ... });
});

Run tests:

npx jest

KMP (Kotlin Multiplatform)

Test location: shared/src/commonTest/kotlin/ for shared code

Run tests:

./gradlew :shared:testDebugUnitTest  # Android target
./gradlew :shared:iosSimulatorArm64Test  # iOS target

When Tests Are Not Possible

Set testsWritten=false if:

  • The fix is in tightly coupled UI code with no way to unit test without massive refactoring
  • The fix is a configuration change (manifest, plist, build file)
  • The fix is purely a resource change (strings, layouts, assets)

Always explain why tests couldn't be written.

04 同仓库更多 Skills