Before you start
Keep the first test small and repeatable.
- A domain function that consumes a normalized capture event.
- A test framework already running in the project.
- A clear rule for what should happen with duplicate, empty and invalid values.
Step by step
Build it in 4 deliberate passes.
Each pass produces a checkable result. Keep device-specific calls in the adapter and keep the workflow portable.
- 01
Depend on your port, not the device class
The receiving workflow should know about CaptureEvent and CapturePort. It should not import a scanner manager, broadcast constant or model-specific type.
- 02
Emit deterministic events
Give the fake adapter explicit methods for a valid scan, duplicate scan, empty value and failure. Tests become readable when the input describes the operator scenario.
- 03
Assert business outcomes
Check stock movement, validation messages and duplicate handling. Do not assert that a vendor callback was called from a business test; that belongs in the hardware adapter test.
- 04
Keep the hardware test small
When a device is available, run a focused smoke test for opening, triggering, decoding and closing. The larger workflow suite should remain fast and device-free.
Copyable pattern / Kotlin
Start with the boundary, then bind the device.
Original test double. It is safe to publish because it does not include a vendor class or binary.
class FakeCapturePort : CapturePort {
private var listener: ((CaptureEvent) -> Unit)? = null
override fun open() = Unit
override fun start() = Unit
override fun stop() = Unit
override fun close() = Unit
override fun setListener(listener: (CaptureEvent) -> Unit) {
this.listener = listener
}
fun emit(value: String, symbology: String? = "test") {
listener?.invoke(CaptureEvent(value, symbology, Instant.now()))
}
}
@Test
fun receiving_a_valid_barcode_updates_stock() {
val scanner = FakeCapturePort()
val workflow = ReceivingWorkflow(scanner)
workflow.start()
scanner.emit("SKU-001")
assertEquals(ReceivingState.Accepted("SKU-001"), workflow.state)
}When the happy path breaks
Make recovery part of the first implementation.
Operators experience the failure state, not the API call. Translate device signals into a useful next action.
Move hardware setup into a small adapter smoke test and keep domain rules on the fake port.
Add an explicit duplicate event and assert the state transition once.
Reject at the normalization boundary and expose a user-recoverable state.
Before you ship
Use this checklist on the target configuration.
- 01
Run the fake adapter tests on every CI build.
- 02
Cover valid, empty, duplicate, oversized and transport-failure cases.
- 03
Keep the device-specific adapter behind one package boundary.
- 04
Run one physical-device smoke test separately for every approved baseline.
Further reading
Use platform guidance for the parts the device SDK does not own.
These references cover Android lifecycle, broadcast, testing and architecture patterns. Device-specific compatibility still needs a model-level validation record.