# Mobiai Write Tests

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

- Skill: `arisguimera/mobiai-write-tests` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arisguimera/mobiai-write-tests`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arisguimera/mobiai-write-tests/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: arisguimera (https://skillmd.com/u/arisguimera)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/arisguimera/mobiai-write-tests

---


# 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**:

```bash
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**:

```bash
# 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):

```bash
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
```kotlin
@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**:
```bash
./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
```swift
func testCalculateTotal_returnsZero_whenCartIsEmpty() { ... }
func testFormatPrice_throws_whenAmountIsNegative() { ... }
```

**Run tests**:
```bash
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
```dart
test('calculateTotal returns zero when cart is empty', () { ... });
group('formatPrice', () {
  test('throws when amount is negative', () { ... });
});
```

**Run tests**:
```bash
flutter test
```

### React Native (TypeScript/JavaScript)

**Default framework**: Jest

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

**Naming**: `describe` + `it` blocks
```typescript
describe('calculateTotal', () => {
  it('returns zero when cart is empty', () => { ... });
});
```

**Run tests**:
```bash
npx jest
```

### KMP (Kotlin Multiplatform)

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

**Run tests**:
```bash
./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.

