| name | unit-test-writer |
| description | Use when writing or extending unit tests for Camposer — covers test infrastructure, fake classes, naming conventions, and patterns for both simple (Type A) and hardware-applied (Type B) camera properties. |
Unit Tests
Camposer tests run in commonTest against fake implementations of the camera engine. No mocks, no platform hardware — all tests are hermetic and cross-platform.
Test Infrastructure
For full fake class details, file paths, and the 3-file rule see reference/FAKE_PATTERNS.md.
| Class | Role |
|---|
CameraSessionTest | Abstract base class — provides cameraSession, cameraTest, controller, initCameraSession(), updateSession() |
FakeCameraTest | Records hardware calls and exposes assert*() helpers; configure isXxxSupported flags before initCameraSession() |
FakeCameraSession | Fake session wired to FakeCameraEngine |
Location: camposer/src/commonTest/kotlin/com/ujizin/camposer/session/
Imports needed:
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse
import kotlinx.coroutines.test.runTest
Naming Conventions
- File:
CameraXxxTest.kt
- Class:
internal class CameraXxxTest : CameraSessionTest()
- Methods:
fun test_preview_xxx_yyy() — snake_case, describe the scenario
Type A — Simple Property (e.g. ScaleType)
Property set via updateSession(). No capability check.
internal class CameraScaleTypeTest : CameraSessionTest() {
@Test
fun test_preview_scale_type() {
initCameraSession()
ScaleType.entries.forEach { scaleType ->
updateSession(scaleType = scaleType)
assertEquals(scaleType, cameraSession.state.scaleType.value)
}
}
}
Pattern: initCameraSession() → updateSession(xxx = value) → assertEquals(state.xxx.value, value)
Type B — Hardware-Applied Property (e.g. FlashMode)
Property set via controller.setXxx(). May have a capability check (isXxxSupported).
internal class CameraFlashModeTest : CameraSessionTest() {
@Test
fun test_preview_all_flash_mode() {
initCameraSession()
FlashMode.entries.forEach { expected ->
controller.setFlashMode(expected)
assertFlashMode(expected)
assertTrue(cameraSession.info.state.value.isFlashSupported)
}
}
@Test
fun test_preview_flash_mode_on_with_no_support() {
cameraTest.isFlashSupported = false
initCameraSession()
val result = controller.setFlashMode(FlashMode.On)
assertTrue(result.isFailure)
assertFalse(cameraSession.info.state.value.isFlashSupported)
assertFlashMode(FlashMode.Off)
}
private fun assertFlashMode(expected: FlashMode) {
cameraTest.assertFlashMode(expected)
assertEquals(expected, cameraSession.state.flashMode.value)
}
}
Pattern: initCameraSession() → controller.setXxx(value) → cameraTest.assertXxx(value) + assertEquals(state.xxx.value, value)
Capability-unsupported pattern: set cameraTest.isXxxSupported = false → initCameraSession() → controller.setXxx(...) → assertTrue(result.isFailure) → assert state unchanged.
Async Tests (e.g. Zoom)
Wrap in runTest when the property flows through coroutines:
@Test
fun test_preview_zoom_change() = runTest {
initCameraSession()
val expected = 4F
cameraSession.controller.setZoomRatio(expected)
cameraTest.assertZoomRatio(expected)
assertEquals(expected, cameraSession.state.zoomRatio.value)
}
Required Test Cases
For every new property, cover:
- Happy path — each enum value (or representative values) applied correctly
- Unsupported capability —
result.isFailure, state unchanged (Type B with capability check only)
- Boundary values — min/max for numeric properties (zoom, exposure)
Common Mistakes
| Mistake | Fix |
|---|
Setting cameraTest.isXxxSupported after initCameraSession() | Must be set BEFORE — fake reads flags at init |
Asserting only cameraSession.state without cameraTest.assertXxx() | Always assert both — state and fake recording |
Using runTest for sync property tests | Only needed for properties that emit through coroutine flows asynchronously |
Checking state before calling controller.setXxx() | Call the setter first, then assert |