Writing Android Tests and Debugging With Claude Code: A Practical Workflow

The Part of Android Development “Nobody Enjoys” :P

Writing tests and debugging are the two parts of Android development where most developers lose the most time. Tests feel like writing code twice, and debugging often means staring at a stack trace while trying to hold an entire call graph in your head. Claude Code doesn’t eliminate either task, but it changes the experience significantly. It can draft test cases from a description, explain why a crash is happening at a specific line, and suggest fixes grounded in your actual codebase — not generic Android advice.

Close-up of code on a screen
Claude Code reads your production code and writes tests that match your actual dependencies and patterns.

Writing ViewModel Tests With Claude Code

The most valuable Android unit tests are ViewModel tests — they cover your business logic, run fast on the JVM, and don’t require an emulator. The hardest part is usually the setup: faking the coroutine dispatcher, mocking the right dependencies, and setting up the Turbine or StateFlow collection correctly. Claude Code handles all of that if you give it the ViewModel source and your testing libraries.

> Read HomeViewModel.kt and HomeUseCase.kt.
> Write a JUnit 5 test class for HomeViewModel using:
> - MockK for mocking (mockk<>, every {}, verify {})
> - Turbine for testing StateFlow (viewModel.uiState.test {})
> - kotlinx-coroutines-test with StandardTestDispatcher and runTest
> - @BeforeEach to set Dispatchers.Main to the test dispatcher
> Cover: initial loading state, success with data, error with message, retry after error.

Claude Code reads the actual ViewModel, sees its constructor dependencies, and generates a test class that mocks exactly the right things — no generic template that doesn’t compile because it guessed the wrong class names. The output looks like this in practice:

@ExtendWith(MockKExtension::class)
class HomeViewModelTest {

    private val testDispatcher = StandardTestDispatcher()
    private val useCase: GetArticlesUseCase = mockk()
    private lateinit var viewModel: HomeViewModel

    @BeforeEach
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        viewModel = HomeViewModel(useCase)
    }

    @AfterEach
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `initial state shows loading`() = runTest {
        viewModel.uiState.test {
            assertThat(awaitItem().isLoading).isTrue()
            cancelAndIgnoreRemainingEvents()
        }
    }

    @Test
    fun `success state contains articles`() = runTest {
        val articles = listOf(Article(id = "1", title = "Test"))
        every { useCase() } returns flowOf(articles)

        viewModel.load()
        testDispatcher.scheduler.advanceUntilIdle()

        viewModel.uiState.test {
            val state = awaitItem()
            assertThat(state.isLoading).isFalse()
            assertThat(state.articles).isEqualTo(articles)
        }
    }
}

A Test-Writing Skill for Consistency

Once you’ve dialled in the prompt that works for your project, turn it into a skill so every developer on the team gets the same output. Create .claude/skills/write-tests/SKILL.md:

# Write Android Unit Tests

Given a ViewModel or UseCase file, write a complete JUnit 5 test class.

## Test setup rules
- Use MockK (never Mockito)
- Use Turbine for StateFlow/SharedFlow assertions
- Use kotlinx-coroutines-test with StandardTestDispatcher and runTest
- Set Dispatchers.Main in @BeforeEach, reset in @AfterEach
- Never use Thread.sleep() or delay() — use testDispatcher.scheduler.advanceUntilIdle()

## Test coverage to include
- Initial state
- Success path with realistic data
- Error path with expected error message in UiState
- Any user-triggered action (button click → state change)

## Naming convention
`fun \`{action} {expected result}\`()` — backtick-quoted descriptive names

Ask the user for the file to test before generating.

Debugging With Claude Code: Paste the Stack Trace

For debugging, the most effective pattern is simple: paste the full stack trace plus the relevant source file and ask Claude Code to explain what’s happening and propose a fix.

> I'm getting this crash in production. Here's the stack trace:
> [paste full stack trace]
>
> Here's the relevant file: [paste file or reference it by name]
>
> Explain what's causing this crash, where exactly it's triggered,
> and give me two or three ways to fix it with trade-offs for each.

Asking for trade-offs is the key addition. A simple “fix this” often gets the quickest patch. Asking for multiple options with trade-offs gets you a fix you understand and can maintain, plus it surfaces cases where the real problem is upstream of the line that crashed.

Debugging Coroutine Cancellation Issues

Coroutine bugs — jobs not cancelling, UI updates on the wrong thread, exceptions swallowed silently — are notoriously hard to debug because the stack trace points to coroutine internals rather than your code. Claude Code is particularly good at these because it can reason about the whole call graph rather than just the crash line:

> My repository call keeps running even after the user navigates away.
> The ViewModel is cleared (onCleared() is called) but I can still see network requests in the log.
> Here's my ViewModel: [paste] and my Repository: [paste].
> Why isn't cancellation propagating, and how do I fix it?

The answer is almost always one of three things: a viewModelScope that isn’t being used, a coroutine launched in a scope that outlives the ViewModel, or a non-cancellable operation (like a database write) that correctly shouldn’t cancel. Claude Code can tell which one it is from reading the code.

The Explain-Then-Fix Habit

The most important habit to build when debugging with Claude Code is to ask for an explanation before a fix. If you go straight to “fix this”, you get a patch you might not fully understand. If you ask “explain what’s happening” first, you understand the root cause and can evaluate whether the fix is treating the symptom or the disease. It takes one extra turn in the conversation and saves hours of re-debugging the same issue under a different shape.

Summary

Claude Code accelerates test writing and debugging most when you give it full context — the actual source files, the real stack trace, and your testing library preferences. Build a /write-tests skill that encodes your project’s testing conventions, use the explain-then-fix pattern for debugging, and you’ll find both activities start to feel less like interruptions to development and more like natural parts of the workflow.

Obviously all these things can be achieved with other Coding Agents, not only Claude Code, you can chose your favorite and there are even free local LLMs that can be used as long as your machine is up for the challenge :).


This post was written by a human with the help of Claude, an AI assistant by Anthropic.

Scroll to Top