agentleFS
lennonpetricklennonpetrick/android-engineering-skills/android-testing/SKILL.md

android-testing

Skill40 starsChanged 27 days agoMITView on GitHub

Defines testing standards for Android projects, including JUnit 5, Turbine, Mockito or MockK, descriptive backtick test names, parameterized tests, and flow testing patterns. Use this skill when creating, reviewing, or refactoring unit tests for ViewModels, use cases, mappers, calculators, repositories, or other pure Kotlin Android components.

5 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

55 rules. 0 are common, 47 are distinctive, and 8 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 47

    Distinctive

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill to create clear, behavior-focused tests for Android code.

    line 9
  2. Distinctive:

    Test behavior, not implementation.

    line 13
  3. Distinctive:

    Prefer unit tests for business logic, flow orchestration, mappers, and calculators.

    line 26
  4. Distinctive:

    Avoid tests that depend on Android framework behavior unless they are true instrumentation tests.

    line 28
  5. Distinctive:

    Verify the full expected result when the behavior has meaningful structure.

    line 29
  6. Distinctive:

    Use the same naming style across the project.

    line 30
  7. Distinctive:

    Use descriptive backtick names:

    line 34
  8. Distinctive:

    Keep names readable without extra commentary.

    line 45
  9. Distinctive:

    Use one test per scenario.

    line 46
  10. Distinctive:

    Prefer names that read like a sentence.

    line 47
  11. Very specific:

    Always name the subject being tested sut.

    line 51
  12. Very specific:

    Use sut consistently in every test file.

    line 61
  13. Distinctive:

    Keep all other mocks and test fixtures named by their actual role.

    line 62
  14. Distinctive:

    Avoid vague names for dependencies.

    line 63
  15. Distinctive:

    create objects and mocks

    line 69
  16. Distinctive:

    Keep arrange, act, and assert separated.

    line 104
  17. Very specific:

    Use expected name for the object expected to be the result.

    line 105
  18. Very specific:

    Use actual name for the resultof the tested function.

    line 106
  19. Distinctive:

    Keep mock setup close to the scenario it supports.

    line 108
  20. Distinctive:

    Test one behavior per test unless a group of values is intentionally parameterized.

    line 109
  21. Distinctive:

    Prefer complete assertions over partial checks.

    line 113
  22. Distinctive:

    Use partial assertions only when the test is explicitly about a small part of the output and the rest is irrelevant.

    line 127
  23. Distinctive:

    verify ordering when ordering matters

    line 132
  24. Distinctive:

    verify contents when contents matter

    line 133
  25. Distinctive:

    verify exact events when events matter

    line 134
  26. Distinctive:

    Use the following stack:

    line 139
  27. Very specific:

    Use @Test from JUnit 5.

    line 148
  28. Very specific:

    Use runTest for suspending code and Flow collection.

    line 149
  29. Distinctive:

    Use Turbine for emissions, completion, cancellation, and errors.

    line 150
  30. Distinctive:

    Use MockK or Mockito-Kotlin consistently within the same project.

    line 151
  31. Distinctive:

    Use Turbine whenever testing Flow or StateFlow behavior.

    line 155
  32. Distinctive:

    Test cancellation when relevant.

    line 184
  33. Distinctive:

    Test replay and state updates when using StateFlow or SharedFlow.

    line 185
  34. Distinctive:

    Do not ignore intermediate emissions if they are part of the contract.

    line 186
  35. Distinctive:

    Use parameterized approaches whenever multiple input and output combinations exist.

    line 190
  36. Very specific:

    Use @TestFactory or dynamic tests when scenarios are naturally data driven and the body should stay compact.

    line 200
  37. Distinctive:

    Use parameterized tests for repeated logic with different values.

    line 219
  38. Distinctive:

    Use dynamic tests when the cases are easy to list as data.

    line 220
  39. Distinctive:

    Keep scenario names meaningful.

    line 221
  40. Distinctive:

    Prefer small, readable data sets.

    line 222
  41. Very specific:

    Use runTest for all coroutine based tests.

    line 226
  42. Distinctive:

    avoid real delays when possible

    line 230
  43. Distinctive:

    keep test setup isolated from production threading behavior

    line 234
  44. Distinctive:

    Test ViewModels through their public API.

    line 247
  45. Distinctive:

    do not assert private implementation details

    line 257
  46. Distinctive:

    use Turbine for state and event flows

    line 260
  47. Distinctive:

    keep user scenarios readable through test names

    line 261
  48. Distinctive:

    Test the input to output contract.

    line 267
  49. Distinctive:

    Test exact mapped values.

    line 271
  50. Distinctive:

    Test interaction with dependencies and returned domain results.

    line 275
  51. Distinctive:

    verify repository output shape

    line 280
  52. Distinctive:

    test both success and failure paths

    line 281
  53. Distinctive:

    test fallback behavior when relevant

    line 282
  54. Distinctive:

    Before merging a test, confirm:

    line 286
  55. Distinctive:

    If a test is hard to read, hard to extend, or tied too closely to implementation details, rewrite it.

    line 297

“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Android Testing

Use this skill to create clear, behavior-focused tests for Android code.

1. Testing principles

Test behavior, not implementation.

Priorities:

  • correctness
  • readability
  • explicit expectations
  • stable test names
  • complete assertions
  • easy diagnosis when failures happen

Rules:

  • Prefer unit tests for business logic, flow orchestration, mappers, and calculators.
  • Keep tests deterministic.
  • Avoid tests that depend on Android framework behavior unless they are true instrumentation tests.
  • Verify the full expected result when the behavior has meaningful structure.
  • Use the same naming style across the project.

2. Test naming

Use descriptive backtick names:

@Test
fun `When amount is valid then it returns parsed value`() { }

Rules:

  • Describe the condition first.
  • Describe the expected result second.
  • Keep names readable without extra commentary.
  • Use one test per scenario.
  • Prefer names that read like a sentence.

3. System under test naming

Always name the subject being tested sut.

Example:

private lateinit var sut: ExchangeViewModel

Rules:

  • Use sut consistently in every test file.
  • Keep all other mocks and test fixtures named by their actual role.
  • Avoid vague names for dependencies.

4. Test structure

Organize tests into four blocks:

  1. create objects and mocks
  2. stub functions
  3. execute tested function
  4. assert result

Example:

private val repository = mockk<ExampleRepository>()
private val sut = ExampleUseCase(repository)

@Test
fun `when repository returns data then ui state is loaded`() = runTest {
    // create objects and mocks
    val text = "mocked text"
    val textA = "A"
    val textB = "B"
    val param = mockk<Param>() {
        every { this.text } returns text
    }
    val expected = listOf(textA, textB)

    // stub functions
    every { repository.getData(param) } returns flowOf(Result.success(listOf(textA, textB)))

    // execute tested function
    val actual = sut(text)

    // assert result
    assertEquals(expected = expected, actual = actual.first().getOrThrow())
}

Rules:

  • Keep arrange, act, and assert separated.
  • Use expected name for the object expected to be the result.
  • Use actual name for the resultof the tested function.
  • Assert expected against actual.
  • Keep mock setup close to the scenario it supports.
  • Test one behavior per test unless a group of values is intentionally parameterized.

5. Assertion style

Prefer complete assertions over partial checks.

Good:

assertEquals(expected = expectedUiState, actual = actualUiState)

Better when collections matter:

assertEquals(expected = expectedItems, actual = actualItems)

Use partial assertions only when the test is explicitly about a small part of the output and the rest is irrelevant.

Rules:

  • assert full object values when practical
  • verify ordering when ordering matters
  • verify contents when contents matter
  • verify exact events when events matter
  • compare expected and actual explicitly

6. Preferred tools

Use the following stack:

  • JUnit 5
  • Turbine for Flow testing
  • MockK or Mockito-Kotlin for mocking
  • runTest for coroutine tests

Rules:

  • Use @Test from JUnit 5.
  • Use runTest for suspending code and Flow collection.
  • Use Turbine for emissions, completion, cancellation, and errors.
  • Use MockK or Mockito-Kotlin consistently within the same project.

7. Flow testing with Turbine

Use Turbine whenever testing Flow or StateFlow behavior.

Common assertions:

  • awaitItem()
  • awaitComplete()
  • awaitError()
  • expectNoEvents()

Example:

@Test
fun `When flow emits values, then it receives them in order`() = runTest {
    val flow = flowOf(1, 2, 3)

    flow.test {
        assertEquals(expected = 1, actual = awaitItem())
        assertEquals(expected = 2, actual = awaitItem())
        assertEquals(expected = 3, actual = awaitItem())
        awaitComplete()
    }
}

Rules:

  • Test emission order.
  • Test failure behavior.
  • Test cancellation when relevant.
  • Test replay and state updates when using StateFlow or SharedFlow.
  • Do not ignore intermediate emissions if they are part of the contract.

8. Parameterized and generated tests

Use parameterized approaches whenever multiple input and output combinations exist.

Prefer:

  • @ParameterizedTest
  • @MethodSource
  • @CsvSource
  • @EnumSource
  • @TestFactory for dynamic tests

Use @TestFactory or dynamic tests when scenarios are naturally data driven and the body should stay compact.

Example:

@TestFactory
fun `When amounts are invalid, then parser returns zero`() = listOf(
    "a" to BigDecimal.ZERO,
    "" to BigDecimal.ZERO,
).map { (input, expected) ->
    dynamicTest("input = $input") {
        val actual = sut.parse(input)
        assertEquals(expected = expected, actual = actual)
    }
}

Rules:

  • Use parameterized tests for repeated logic with different values.
  • Use dynamic tests when the cases are easy to list as data.
  • Keep scenario names meaningful.
  • Prefer small, readable data sets.

9. Coroutine and Flow test patterns

Use runTest for all coroutine based tests.

Rules:

  • avoid real delays when possible
  • inject dispatchers or use test dispatchers
  • advance virtual time when needed
  • cancel collectors when a test scenario requires it
  • keep test setup isolated from production threading behavior

Useful checks:

  • state updates
  • retry behavior
  • cancellation behavior
  • error recovery
  • replay behavior
  • ordering of emissions

10. ViewModel testing

Test ViewModels through their public API.

Prefer testing:

  • onAction
  • uiState
  • uiEvents

Rules:

  • do not assert private implementation details
  • assert the emitted state sequence
  • assert side effects as events
  • use Turbine for state and event flows
  • keep user scenarios readable through test names

11. Repository, use case, and mapper tests

Use cases

Test the input to output contract.

Mappers

Test exact mapped values.

Repositories

Test interaction with dependencies and returned domain results.

Rules:

  • mock external dependencies
  • verify repository output shape
  • test both success and failure paths
  • test fallback behavior when relevant

12. Review checklist

Before merging a test, confirm:

  • the test name explains the behavior
  • sut is used consistently
  • arrange, act, and assert are separated
  • assertions compare expected and actual clearly
  • data driven cases use parameterized or dynamic tests when appropriate
  • Flow tests use Turbine
  • coroutine tests use runTest
  • the test verifies behavior instead of internal implementation

If a test is hard to read, hard to extend, or tied too closely to implementation details, rewrite it.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.