android-tdd

SKILL.md

Required Plugins

Superpowers plugin: MUST be active for all work using this skill. Use throughout the entire build pipeline — design decisions, code generation, debugging, quality checks, and any task where it offers enhanced capabilities. If superpowers provides a better way to accomplish something, prefer it over the default approach.

Android Test-Driven Development (TDD)

Overview

TDD is a development process where you write tests before feature code, following the Red-Green-Refactor cycle. Every feature starts with a failing test, gets minimal implementation, then is refined.

Core Principle: No production code without a failing test first.

Icon Policy: If UI code is generated as part of TDD, use custom PNG icons and maintain PROJECT_ICONS.md (see android-custom-icons).

Report Table Policy: If UI tests cover reports that can exceed 25 rows, the UI must use table layouts (see android-report-tables).

Quick Reference

Topic Reference File When to Use
TDD Workflow references/tdd-workflow.md Step-by-step Red-Green-Refactor with examples
Testing by Layer references/testing-by-layer.md Unit, integration, persistence, network, UI tests
Advanced Techniques references/advanced-techniques.md Factories, behavior verification, LiveData/Flow
Tools & CI Setup references/tools-and-ci.md Dependencies, CI pipelines, test configuration
Team Adoption references/team-adoption.md Legacy code, team onboarding, troubleshooting

The Red-Green-Refactor Cycle

1. RED    → Write a failing test for desired behavior
2. GREEN  → Write MINIMUM code to make it pass
3. REFACTOR → Clean up while keeping tests green
4. REPEAT → Next behavior

Critical Rules:

  • Never skip the Red phase (verify the test actually fails)
  • Never write more code than needed in Green phase
  • Never refactor with failing tests
  • Each cycle should take minutes, not hours

Test Pyramid (70/20/10)

        /  UI  \        10% - Espresso, end-to-end flows
       /--------\
      / Integra- \      20% - ViewModel+Repository, Room, API
     /  tion      \
    /--------------\
   /   Unit Tests   \   70% - Pure Kotlin, fast, isolated
  /==================\
Type Speed Scope Location Tools
Unit <1ms each Single class/method test/ JUnit, Mockito
Integration ~100ms each Component interactions test/ or androidTest/ JUnit, Robolectric
UI ~1s each User flows androidTest/ Espresso, Compose Testing

TDD Workflow for Android Features

Step 1: Define the Requirement

Start with a clear user story or acceptance criteria:

As a user, I want to add items to my cart so I can purchase them later.

Step 2: Write the Failing Test (Red)

@Test
fun addItemToCart_increasesCartCount() {
    val cart = ShoppingCart()
    cart.addItem(Product("Phone", 999.99))
    assertEquals(1, cart.itemCount)
}

Run it. It must fail (class doesn't exist yet).

Step 3: Write Minimal Code (Green)

class ShoppingCart {
    private val items = mutableListOf<Product>()
    fun addItem(product: Product) { items.add(product) }
    val itemCount: Int get() = items.size
}

Run test. It passes. Stop writing code.

Step 4: Add Next Test, Then Refactor

@Test
fun addMultipleItems_calculatesTotal() {
    val cart = ShoppingCart()
    cart.addItem(Product("Phone", 999.99))
    cart.addItem(Product("Case", 29.99))
    assertEquals(1029.98, cart.totalPrice, 0.01)
}

Implement totalPrice, then refactor both test and production code.

Layer-Specific Testing Summary

Unit Tests (Domain & ViewModel)

class ScoreTest {
    @Test
    fun increment_increasesCurrentScore() {
        val score = Score()
        score.increment()
        assertEquals(1, score.current)
    }
}
  • Mock all dependencies with Mockito
  • Test one behavior per test
  • No Android framework dependencies

Integration Tests (Repository + Database)

@RunWith(AndroidJUnit4::class)
class WishlistDaoTest {
    private lateinit var db: AppDatabase

    @Before
    fun setup() {
        db = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            AppDatabase::class.java
        ).build()
    }

    @After
    fun teardown() { db.close() }
}

Network Tests (API Layer)

class ApiServiceTest {
    private val mockWebServer = MockWebServer()

    @Test
    fun fetchData_returnsExpectedResponse() {
        mockWebServer.enqueue(
            MockResponse().setBody("""{"id":1,"name":"Test"}""").setResponseCode(200)
        )
        val response = service.fetchData().execute()
        assertEquals("Test", response.body()?.name)
    }
}

UI Tests (Espresso / Compose)

@Test
fun clickSaveButton_showsConfirmation() {
    onView(withId(R.id.saveButton)).perform(click())
    onView(withText("Saved!")).check(matches(isDisplayed()))
}

Essential Test Dependencies

dependencies {
    // Unit
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'

    // Integration & UI
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'

    // Room & Network
    testImplementation 'androidx.room:room-testing:2.6.1'
    testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
}

Test Naming Convention

Use descriptive names following: methodUnderTest_condition_expectedResult

fun addItem_emptyCart_cartHasOneItem()
fun calculateTotal_multipleItems_returnsSumOfPrices()
fun login_invalidCredentials_returnsError()
fun fetchUsers_networkError_showsErrorState()

Patterns & Anti-Patterns

DO

  • Write tests first (always Red before Green)
  • Keep tests small and focused (one assertion per concept)
  • Use descriptive test names that document behavior
  • Use test data factories for complex objects
  • Test edge cases and error conditions
  • Refactor tests alongside production code

DON'T

  • Test implementation details (test behavior, not internals)
  • Write tests for generated code (Hilt, Room DAOs)
  • Test third-party libraries (Retrofit, Gson)
  • Chase 100% coverage at expense of test quality
  • Write slow, flaky, or order-dependent tests
  • Skip the Red phase (you won't catch false positives)

Integration with Other Skills

feature-planning → Define specs & acceptance criteria
android-tdd → Write tests first, then implement (THIS SKILL)
android-development → Follow architecture & Kotlin standards
ai-error-handling → Validate AI-generated implementations
vibe-security-skill → Security review

Key Integrations:

  • android-development: Follow MVVM + Clean Architecture for testable design
  • feature-planning: Use acceptance criteria as test scenarios
  • ai-error-handling: Validate AI output against test expectations
  • superpowers:test-driven-development: General TDD workflow orchestration

CI Pipeline

name: Android TDD
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Unit Tests
        run: ./gradlew test
      - name: Instrumented Tests
        run: ./gradlew connectedAndroidTest
      - name: Coverage Report
        run: ./gradlew jacocoTestReport

CI Rules:

  • All tests must pass before merge
  • Coverage reports generated on every PR
  • Unit tests and instrumented tests run in parallel

References

  • Google Testing Guide: developer.android.com/training/testing
  • Mockito Kotlin: github.com/mockito/mockito-kotlin
  • Espresso: developer.android.com/training/testing/espresso
  • Architecture Samples: github.com/android/architecture-samples
Weekly Installs
18
GitHub Stars
3
First Seen
Feb 17, 2026
Installed on
amp18
github-copilot18
codex18
kimi-cli18
gemini-cli18
opencode18