
testing-android-code
This skill should be used when writing or reviewing tests for Android code in Bitwarden. Triggered by "BaseViewModelTest", "BitwardenComposeTest", "BaseServiceTest", "stateEventFlow", "bufferedMutableSharedFlow", "FakeDispatcherManager", "expectNoEvents", "assertCoroutineThrows", "createMockCipher", "createMockSend", "asSuccess", "Why is my Bitwarden test failing?", or testing questions about ViewModels, repositories, Compose screens, or data sources in Bitwarden.
- Installs
- 76
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/bitwarden/android --skill testing-android-code- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads testing-android-code automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Testing Android Code - Bitwarden Testing Patterns
This skill provides tactical testing guidance for Bitwarden-specific patterns. For comprehensive architecture and testing philosophy, consult docs/ARCHITECTURE.md.
Test Framework Configuration
Required Dependencies:
- JUnit 5 (jupiter), MockK, Turbine (app.cash.turbine)
- kotlinx.coroutines.test, Robolectric, Compose Test
Critical Note: Tests run with en-US locale for consistency. Don't assume other locales.
A. ViewModel Testing Patterns
Base Class: BaseViewModelTest
Always extend BaseViewModelTest for ViewModel tests.
Location: ui/src/testFixtures/kotlin/com/bitwarden/ui/platform/base/BaseViewModelTest.kt
Benefits:
- Automatically registers
MainDispatcherExtensionforUnconfinedTestDispatcher - Provides
stateEventFlow()helper for simultaneous StateFlow/EventFlow testing
Pattern:
class ExampleViewModelTest : BaseViewModelTest() {
private val mockRepository: ExampleRepository = mockk()
private val savedStateHandle = SavedStateHandle(mapOf(KEY_STATE to INITIAL_STATE))
@Test
fun `ButtonClick should fetch data and update state`() = runTest {
coEvery { mockRepository.fetchData(any()) } returns Result.success("data")
val viewModel = ExampleViewModel(savedStateHandle, mockRepository)
viewModel.stateFlow.test {
assertEquals(INITIAL_STATE, awaitItem())
viewModel.trySendAction(ExampleAction.ButtonClick)
assertEquals(INITIAL_STATE.copy(data = "data"), awaitItem())
}
coVerify { mockRepository.fetchData(any()) }
}
}
For complete examples: See references/test-base-classes.md
StateFlow vs EventFlow (Critical Distinction)
| Flow Type | Replay | First Action | Pattern |
|---|---|---|---|
| StateFlow | Yes (1) | awaitItem() gets current state | Expect initial → trigger → expect new |
| EventFlow | No | expectNoEvents() first | expectNoEvents → trigger → expect event |
For detailed patterns: See references/flow-testing-patterns.md
B. Compose UI Testing Patterns
Base Class: BitwardenComposeTest
Always extend BitwardenComposeTest for Compose screen tests.
Location: app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/base/BitwardenComposeTest.kt
Benefits:
- Pre-configures all Bitwarden managers (FeatureFlags, AuthTab, Biometrics, etc.)
- Wraps content in
BitwardenThemeandLocalManagerProvider - Provides fixed Clock for deterministic time-based tests
Pattern:
class ExampleScreenTest : BitwardenComposeTest() {
private var haveCalledNavigateBack = false
private val mutableEventFlow = bufferedMutableSharedFlow<ExampleEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val viewModel = mockk<ExampleViewModel>(relaxed = true) {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
@Before
fun setup() {
setContent {
ExampleScreen(
onNavigateBack = { haveCalledNavigateBack = true },
viewModel = viewModel,
)
}
}
@Test
fun `on back click should send BackClick action`() {
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(ExampleAction.BackClick) }
}
}
Note: Use bufferedMutableSharedFlow for event testing in Compose tests. Default replay is 0; pass replay = 1 if needed.
For complete base class details: See references/test-base-classes.md
C. Repository and Service Testing
Service Testing with MockWebServer
Base Class: BaseServiceTest (network/src/testFixtures/)
class ExampleServiceTest : BaseServiceTest() {
private val api: ExampleApi = retrofit.create()
private val service = ExampleServiceImpl(api)
@Test
fun `getConfig should return success when API succeeds`() = runTest {
server.enqueue(MockResponse().setBody(EXPECTED_JSON))
val result = service.getConfig()
assertEquals(EXPECTED_RESPONSE.asSuccess(), result)
}
}
Repository Testing Pattern
class ExampleRepositoryTest {
private val fixedClock: Clock = Clock.fixed(
Instant.parse("2023-10-27T12:00:00Z"),
ZoneOffset.UTC,
)
private val dispatcherManager = FakeDispatcherManager()
private val mockDiskSource: ExampleDiskSource = mockk()
private val mockService: ExampleService = mockk()
private val repository = ExampleRepositoryImpl(
clock = fixedClock,
exampleDiskSource = mockDiskSource,
exampleService = mockService,
dispatcherManager = dispatcherManager,
)
@Test
fun `fetchData should return success when service succeeds`() = runTest {
coEvery { mockService.getData(any()) } returns expectedData.asSuccess()
val result = repository.fetchData(userId)
assertTrue(result.isSuccess)
}
}
Key patterns: Use FakeDispatcherManager, fixed Clock, and .asSuccess() helpers.
D. Test Data Builders
Builder Pattern with Number Parameter
Location: network/src/testFixtures/kotlin/com/bitwarden/network/model/
fun createMockCipher(
number: Int,
id: String = "mockId-$number",
name: String? = "mockName-$number",
// ... more parameters with defaults
): SyncResponseJson.Cipher
// Usage:
val cipher1 = createMockCipher(number = 1) // mockId-1, mockName-1
val cipher2 = createMockCipher(number = 2) // mockId-2, mockName-2
val custom = createMockCipher(number = 3, name = "Custom")
Available Builders (35+):
- Cipher:
createMockCipher(),createMockLogin(),createMockCard(),createMockIdentity(),createMockSecureNote(),createMockSshKey(),createMockField(),createMockUri(),createMockFido2Credential(),createMockPasswordHistory(),createMockCipherPermissions() - Sync:
createMockSyncResponse(),createMockFolder(),createMockCollection(),createMockPolicy(),createMockDomains() - Send:
createMockSend(),createMockFile(),createMockText(),createMockSendJsonRequest() - Profile:
createMockProfile(),createMockOrganization(),createMockProvider(),createMockPermissions() - Attachments:
createMockAttachment(),createMockAttachmentJsonRequest(),createMockAttachmentResponse()
See network/src/testFixtures/kotlin/com/bitwarden/network/model/ for full list.
E. Result Type Testing
Locations:
.asSuccess(),.asFailure():core/src/main/kotlin/com/bitwarden/core/data/util/ResultExtensions.ktassertCoroutineThrows:core/src/testFixtures/kotlin/com/bitwarden/core/data/util/TestHelpers.kt
// Create results
"data".asSuccess() // Result.success("data")
throwable.asFailure() // Result.failure<T>(throwable)
// Assertions
assertTrue(result.isSuccess)
assertEquals(expectedValue, result.getOrNull())
F. Test Utilities and Helpers
Fake Implementations
| Fake | Location | Purpose |
|---|---|---|
FakeDispatcherManager | core/src/testFixtures/ | Deterministic coroutine execution |
FakeConfigDiskSource | data/src/testFixtures/ | In-memory config storage |
FakeSharedPreferences | data/src/testFixtures/ | Memory-backed SharedPreferences |
Exception Testing (CRITICAL)
// CORRECT - Call directly, NOT inside runTest
@Test
fun `test exception`() {
assertCoroutineThrows<IllegalStateException> {
repository.throwingFunction()
}
}
Why: runTest catches exceptions and rethrows them, breaking the assertion pattern.
G. Critical Gotchas
Common testing mistakes in Bitwarden. For complete details and examples: See references/critical-gotchas.md
Core Patterns:
- assertCoroutineThrows + runTest - Never wrap in
runTest; call directly - Static mock cleanup - Always
unmockkStatic()in@After - StateFlow vs EventFlow - StateFlow:
awaitItem()first; EventFlow:expectNoEvents()first - FakeDispatcherManager - Always use instead of real
DispatcherManagerImpl - Coroutine test wrapper - Use
runTest { }for all Flow/coroutine tests
Assertion Patterns:
- Complete state assertions - Assert entire state objects, not individual fields
- JUnit over Kotlin - Use
assertTrue(), not Kotlin'sassert() - Use Result extensions - Use
asSuccess()andasFailure()for Result type assertions
Test Design:
- Fake vs Mock strategy - Use Fakes for happy paths, Mocks for error paths
- DI over static mocking - Extract interfaces (like UuidManager) instead of mockkStatic
- Null stream testing - Test null returns from ContentResolver operations
- bufferedMutableSharedFlow - Use with
.onSubscription { emit(state) }in Fakes - Test factory methods - Accept domain state types, not SavedStateHandle
H. Test File Organization
Directory Structure
module/src/test/kotlin/com/bitwarden/.../
├── ui/*ScreenTest.kt, *ViewModelTest.kt
├── data/repository/*RepositoryTest.kt
└── network/service/*ServiceTest.kt
module/src/testFixtures/kotlin/com/bitwarden/.../
├── util/TestHelpers.kt
├── base/Base*Test.kt
└── model/*Util.kt
Test Constants Placement
Declare test constants as top-level private const val at the bottom of the file, after the class closing brace. Do NOT use companion object for test constants.
Test Naming
- Classes:
*Test.kt,*ScreenTest.kt,*ViewModelTest.kt - Functions:
`given state when action should result`
Summary
Key Bitwarden-specific testing patterns:
- BaseViewModelTest - Automatic dispatcher setup with
stateEventFlow()helper - BitwardenComposeTest - Pre-configured with all managers and theme
- BaseServiceTest - MockWebServer setup for network testing
- Turbine Flow Testing - StateFlow (replay) vs EventFlow (no replay)
- Test Data Builders - Consistent
number: Intparameter pattern - Fake Implementations - FakeDispatcherManager, FakeConfigDiskSource
- Result Type Testing -
.asSuccess(),.asFailure()
Always consult: docs/ARCHITECTURE.md and existing test files for reference implementations.
Reference Documentation
For detailed information, see:
references/test-base-classes.md- Detailed base class documentation and usage patternsreferences/flow-testing-patterns.md- Complete Turbine patterns for StateFlow/EventFlowreferences/critical-gotchas.md- Full anti-pattern reference and debugging tips
Complete Examples:
examples/viewmodel-test-example.md- Full ViewModel test with StateFlow/EventFlowexamples/compose-screen-test-example.md- Full Compose screen testexamples/repository-test-example.md- Full repository test with mocks and fakes
FAQ
What does the testing-android-code skill do?
This skill should be used when writing or reviewing tests for Android code in Bitwarden. Triggered by "BaseViewModelTest", "BitwardenComposeTest", "BaseServiceTest", "stateEventFlow", "bufferedMutableSharedFlow", "FakeDispatcherManager", "expectNoEvents", "assertCoroutineThrows", "createMockCipher", "createMockSend", "asSuccess", "Why is my Bitwarden test failing?", or testing questions about ViewModels, repositories, Compose screens, or data sources in Bitwarden.
How do I install testing-android-code?
Run npx skills add https://github.com/bitwarden/android --skill testing-android-code in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/testing-android-code/ (personal) or .claude/skills/testing-android-code/ (project).
Which AI agents can use testing-android-code?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is testing-android-code safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains testing-android-code?
It is published by bitwarden in bitwarden/android, and it is an official skill from the maker of the technology it covers. It has 76 recorded installs.
More from bitwarden/android
reviewing-changes
bitwarden/android
Open the skill to read its SKILL.md instructions.
implementing-android-code
bitwarden/android
Open the skill to read its SKILL.md instructions.
build-test-verify
bitwarden/android
Open the skill to read its SKILL.md instructions.
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work