name: testing-guidelines description: 'Testing strategy for unit and instrumentation coverage. Use when writing tests for DataState flows, repositories, ViewModels, and WorkManager logic.'
Skill: Testing Guidelines
Overview
The project is structured so that domain and data layers can be tested with plain JUnit without an
Android device. UI and integration tests belong in androidTest source sets and require an emulator
or device.
Key files to read
buildSrc/src/main/java/co/anitrend/buildSrc/plugins/strategy/DependencyStrategy.kt— auto-adds JUnit, MockK, Turbine, and coroutines-test to every module's test configurationsbuildSrc/src/main/java/co/anitrend/buildSrc/plugins/components/ProjectDependencies.kt— shows where WorkManager test utilities are referenced (currently commented out).agents/skills/mockk-testing-patterns/SKILL.md— repo-specific MockK workflow, decision points, plus extended examples for flow assertions, worker logic coverage, and Koin module validation.agents/skills/koin-module-wiring/SKILL.md— binding patterns and module aggregation rules.agents/skills/testing-guidelines/references/koin-testing.md— Koin test primitives and the repo's current DI graph verification pattern
Unit tests (test/)
- Mirror the production package name inside
src/test/kotlin/. - Use JUnit 4 as the base runner.
- Use MockK to mock dependencies; prefer mocking interfaces over concrete classes for stability across implementation changes.
- Use Turbine to assert
Flow/DataStateemissions:dataState.data.test { assertEquals(expectedItem, awaitItem()) cancelAndIgnoreRemainingEvents() } - Use
kotlinx-coroutines-test(runTest,StandardTestDispatcher, orUnconfinedTestDispatcher) for any suspend-function tests. - All test dependencies are injected automatically — no manual
testImplementationdeclarations needed for the standard stack.
Source set decision
- Place tests in
src/testby default. - Move to
androidTestonly when the code under test directly invokes Room, ContentResolver, WorkManager scheduling, or another Android runtime dependency that cannot be instantiated in a plain JVM test.
Koin testing
- Use
KoinTestwhen the test needs Koin-backed lookup viaget()orby inject(). - For data-layer DI changes, add a focused
src/testresolution test for the changed binding or writer path. Start Koin with only the module(s) that directly declare or aggregate the binding under test, provide mocks for unrelated collaborators, and assert that the concrete binding resolves successfully. - Use the
androidTestgraph-check pattern fromdata/src/androidTest/kotlin/co/anitrend/data/android/koin/ModulesTest.ktas a secondary regression guard for central:datawiring and app-wide aggregators. - When a test needs to replace one binding inside an active Koin context, prefer Koin test
utilities such as
declareMockordeclareinstead of editing production modules just for test coverage. - Use Koin graph checks to validate that module wiring resolves correctly; use regular unit tests to validate behavior of the resolved objects.
- Keep graph verification narrow. Load only the module(s) that directly declare or aggregate the binding under test, plus the minimum stub modules needed to satisfy its declared dependencies.
- If you add or refactor a module aggregator, writer factory, embed mapper dependency, or worker binding, add or update a Koin validation test alongside it.
Instrumented tests (androidTest/)
- Use Espresso for UI interaction on critical flows.
- Use Koin test modules to inject mocks or fakes into the DI graph during tests.
- Document emulator/device requirements in the PR description.
WorkManager tasks
- Test worker logic by calling the internal logic directly (without the WorkManager runtime) where possible.
- Use
WorkManagerTestInitHelperfor end-to-end worker tests if needed.
Running tests
# Unit tests (all modules, debug variant)
./gradlew testDebugUnitTest --no-daemon
# Instrumented tests (requires connected device/emulator)
./gradlew connectedDebugAndroidTest --no-daemon
# Check formatting before submitting
./gradlew spotlessCheck
Best practices
- Write pure functions in use cases to simplify unit testing.
- For offline-first fixed-size cached reads, add at least one unit test that proves the local entity converter or mapper preserves the UI-facing fields the screen depends on.
- When a mapper intentionally persists empty collections to avoid repeat fetches, cover that path with a unit test or an integration-style mapper test.
- If a cache key varies by request shape, add an assertion around the variant identity or the converter output that makes the distinction explicit.
- For dependency wiring work, add a Koin validation test so broken bindings fail before runtime.
- For central
:datamodule wiring, prefer a binding-specific unit test insrc/testthat proves the changed definition resolves, then keep the broader graph check as additional coverage. - Do not remove or modify existing tests to make a PR pass; fix the underlying code instead.
- Include test results or a brief pass/fail note in the PR checklist.