Write Unit Tests
Overview
Unit tests in Alkaa are multiplatform-first, fake-based, and structured around a single scenario per test. Tests live in commonTest and run fastest via ./gradlew desktopTest.
Structure
- Given / When / Then — Use comments to separate the three blocks in every test → see
references/TEST_EXAMPLES.md - Backtick names — Start with
test iforcheck if, describe the specific condition and expected outcome - One scenario per test — Split side effects, error paths, and edge cases into separate tests
- Fakes only — Never mocks for domain interfaces → see
references/FAKE_PATTERNS.mdfor all three fake patterns - CoroutinesTestDispatcher — Delegate
by CoroutinesTestDispatcherImpl()for any coroutine-using test class → seereferences/TEST_EXAMPLES.md - ViewModel test setup → see
references/TEST_EXAMPLES.md - Use case / repository test setup → see
references/TEST_EXAMPLES.md
Naming
✅ `test if when load tasks fails the error state is returned`
✅ `test if task is updated as completed`
✅ `test if category without name is not added`
❌ testUpdateTask()
❌ test1()
Rules
| Rule | Details |
|---|---|
| Fakes over mocks | Only use mocks for Android/Framework types you cannot interface (e.g., Context) |
| CoroutinesTestDispatcher | Delegate by CoroutinesTestDispatcherImpl() for any coroutine-using test class |
| ViewModel at field level | Construct ViewModel at field level, not inside @BeforeTest |
runTest for suspend |
Use = runTest { } for all suspend test bodies |
flow.first() |
Collect current state with flow.first() |
require() for narrowing |
Use require(state is X) before accessing state-specific properties |
@BeforeTest cleanup |
Call clean() on every fake to reset state between tests |
Common Mistakes
| Mistake | Fix |
|---|---|
| Using mocks for domain interfaces | Create a fake implementing the interface |
Forgetting CoroutinesTestDispatcher delegation |
Add by CoroutinesTestDispatcherImpl() |
| Testing multiple scenarios in one test | Split into separate @Test functions |
Skipping @BeforeTest cleanup |
Add setup() calling clean() on every fake |
Constructing ViewModel in @BeforeTest |
Construct at field level so it shares dispatcher context |
| Omitting Given/When/Then comments | Always add the three comment blocks |
Vague test names like testUpdate() |
Rename to describe the scenario and expected outcome |
Related Skills
- ViewModel tests → use
write-viewmodelskill for ViewModel construction patterns - UI behavior tests → use
write-ui-testsskill - Full user flow tests → use
write-e2e-testsskill