Files
Meshtastic-Android/core/testing/README.md

7.0 KiB

/*

  • Copyright (c) 2026 Meshtastic LLC
  • This program is free software: you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation, either version 3 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program. If not, see https://www.gnu.org/licenses/. */

:core:testing — Shared Test Doubles and Utilities

Purpose

The :core:testing module provides lightweight, reusable test doubles (fakes, builders, factories) and testing utilities for all KMP modules. This module consolidates testing dependencies into a single, well-controlled location to:

  • Reduce duplication: Shared fakes (e.g., FakeNodeRepository, FakeRadioController) used across multiple modules.
  • Keep dependency graph clean: All test doubles and libraries are defined once; modules depend on :core:testing instead of scattered test deps.
  • Enable KMP-wide test patterns: Every module (commonTest, androidUnitTest, JVM tests) can reuse the same fakes.
  • Maintain purity: Core business logic modules (e.g., core:domain, core:data) depend on :core:testing via commonTest, avoiding test-code leakage into production.

Dependency Strategy

┌─────────────────────────────────────┐
│    core:testing                     │
│  (only deps: core:model,            │
│   core:repository, test libs)       │
└──────────────┬──────────────────────┘
               ↑
               │ (commonTest dependency)
        ┌──────┴─────────────┬────────────────────┐
        │                    │                    │
   core:domain          feature:messaging    feature:node
   core:data            feature:settings     feature:firmware
   (etc.)               (etc.)

Target Compatibility Warning (March 2026 Audit)

  • MockK Removal: MockK has been removed from commonMain because it does not natively support Kotlin/Native (iOS).
  • Future-Proofing: The project is migrating to dev.mokkery for KMP-compatible mocking or favoring manual fakes.
  • Recommendation: Favor manual fakes (like FakeNodeRepository) in commonMain to maintain pure KMP portability.

Key Design Rules

  1. :core:testing has NO dependencies on heavy modules: It only depends on:

    • core:model — Domain types (Node, User, etc.)
    • core:repository — Interfaces (NodeRepository, etc.)
    • Test libraries (kotlin("test"), kotlinx.coroutines.test, turbine, junit)
  2. No circular dependencies: Modules that depend on :core:testing (in commonTest) cannot be dependencies of :core:testing itself.

  3. :core:testing is NOT part of the app bundle: It's declared in commonTest sourceSet only, so it never appears in release APKs or final JARs.

What's Included

Test Doubles (Fakes)

FakeRadioController

A no-op implementation of RadioController for unit tests. Tracks method calls and state changes.

val radioController = FakeRadioController()
radioController.setConnectionState(ConnectionState.Connected)
assertEquals(1, radioController.sentPackets.size)

FakeNodeRepository

An in-memory implementation of NodeRepository for isolated testing.

val nodeRepo = FakeNodeRepository()
nodeRepo.setNodes(TestDataFactory.createTestNodes(5))
assertEquals(5, nodeRepo.nodeDBbyNum.value.size)

Test Builders & Factories

TestDataFactory

Factory methods for creating domain objects with sensible defaults.

val node = TestDataFactory.createTestNode(num = 42, longName = "Alice")
val nodes = TestDataFactory.createTestNodes(10)

Test Utilities

Flow collection helper

val emissions = flow { emit(1); emit(2) }.toList()
assertEquals(listOf(1, 2), emissions)

Usage Examples

Testing a ViewModel (in feature:messaging/src/commonTest)

class MessageViewModelTest {
    private val nodeRepository = FakeNodeRepository()

    @Test
    fun testLoadsNodesCorrectly() = runTest {
        nodeRepository.setNodes(TestDataFactory.createTestNodes(3))
        val viewModel = createViewModel(nodeRepository)
        assertEquals(3, viewModel.nodeCount.value)
    }
}

Testing a UseCase (in core:domain/src/commonTest)

class SendMessageUseCaseTest {
    private val radioController = FakeRadioController()

    @Test
    fun testSendsPacket() = runTest {
        val useCase = SendMessageUseCase(radioController)
        useCase.sendMessage(testPacket)
        assertEquals(1, radioController.sentPackets.size)
    }
}

Adding New Test Doubles

When adding a new fake to :core:testing:

  1. Implement the interface from core:model or core:repository.
  2. Track side effects (e.g., sentPackets, calledMethods) for test assertions.
  3. Provide test helpers (e.g., setNodes(), clear()) to manipulate state.
  4. Document with examples in the class KDoc.

Example:

/**
 * A test double for [SomeRepository].
 */
class FakeSomeRepository : SomeRepository {
    val callHistory = mutableListOf<String>()

    override suspend fun doSomething(value: String) {
        callHistory.add(value)
    }

    // Test helpers
    fun getCallCount() = callHistory.size
    fun clear() = callHistory.clear()
}

Dependency Maintenance

When adding a new module:

  • If it has commonTest tests, add implementation(projects.core.testing) to its commonTest.dependencies.
  • Do NOT add heavy modules (e.g., core:database) to :core:testing's dependencies.

When a test needs a mock:

  • Check :core:testing first for an existing fake.
  • If none exists, consider adding it there (if it's reusable) vs. using mockk() inline.

When updating interfaces:

  • Update corresponding fakes in :core:testing to match new method signatures.
  • Keep fakes no-op; don't replicate business logic.

Files

core/testing/
├── build.gradle.kts              # Lightweight, minimal dependencies
├── README.md                      # This file
└── src/commonMain/kotlin/org/meshtastic/core/testing/
    ├── FakeRadioController.kt     # RadioController test double
    ├── FakeNodeRepository.kt      # NodeRepository test double
    └── TestDataFactory.kt         # Builders and factories

See Also

  • AGENTS.md §3B: KMP platform purity guidelines (relevant for test code).
  • docs/kmp-status.md: KMP module status and targets.
  • .github/copilot-instructions.md: Build and test commands.