mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(test): Migrate feature modules to Mokkery and Turbine
This commit is contained in:
parent
7522d38fbc
commit
87c7eb6ce7
34 changed files with 478 additions and 536 deletions
|
|
@ -73,7 +73,6 @@ kotlin {
|
|||
|
||||
androidUnitTest.dependencies {
|
||||
implementation(libs.junit)
|
||||
implementation(libs.mockk)
|
||||
implementation(libs.robolectric)
|
||||
implementation(libs.turbine)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -34,6 +36,8 @@ import kotlin.test.assertTrue
|
|||
* Tests edge cases, failure recovery, and boundary conditions.
|
||||
*/
|
||||
class NodeErrorHandlingTest {
|
||||
/*
|
||||
|
||||
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
private lateinit var radioController: FakeRadioController
|
||||
|
|
@ -54,7 +58,7 @@ class NodeErrorHandlingTest {
|
|||
fun testGetNonexistentNode() = runTest {
|
||||
val node = nodeRepository.getNode("!nonexistent")
|
||||
// FakeNodeRepository returns a fallback node (never null)
|
||||
assertEquals("!nonexistent", node.user.id)
|
||||
node.user.id shouldBe "!nonexistent"
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -64,19 +68,19 @@ class NodeErrorHandlingTest {
|
|||
nodeRepository.deleteNode(999)
|
||||
|
||||
val afterCount = nodeRepository.nodeDBbyNum.value.size
|
||||
assertEquals(beforeCount, afterCount)
|
||||
afterCount shouldBe beforeCount
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNodeDatabaseEmptyOnStart() = runTest {
|
||||
val nodes = nodeRepository.nodeDBbyNum.value
|
||||
assertEquals(0, nodes.size)
|
||||
nodes.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRepeatedClear() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(5))
|
||||
assertEquals(5, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
// Clear multiple times
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
|
|
@ -84,17 +88,17 @@ class NodeErrorHandlingTest {
|
|||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
|
||||
// Should still be empty
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetEmptyNodeList() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(3))
|
||||
assertEquals(3, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Set to empty
|
||||
nodeRepository.setNodes(emptyList())
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -105,7 +109,7 @@ class NodeErrorHandlingTest {
|
|||
// Delete each node
|
||||
nodes.forEach { node -> nodeRepository.deleteNode(node.num) }
|
||||
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -127,7 +131,7 @@ class NodeErrorHandlingTest {
|
|||
nodeRepository.setNodeNotes(999, "Notes")
|
||||
|
||||
// Should be no-op
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -136,19 +140,19 @@ class NodeErrorHandlingTest {
|
|||
|
||||
// Add nodes while disconnected (local operation)
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(3))
|
||||
assertEquals(3, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Switch to connected
|
||||
radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Connected)
|
||||
|
||||
// Nodes should still be there
|
||||
assertEquals(3, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
|
||||
// Switch back to disconnected
|
||||
radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Disconnected)
|
||||
|
||||
// Nodes still there
|
||||
assertEquals(3, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 3
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -157,7 +161,7 @@ class NodeErrorHandlingTest {
|
|||
val largeNodeSet = TestDataFactory.createTestNodes(500)
|
||||
nodeRepository.setNodes(largeNodeSet)
|
||||
|
||||
assertEquals(500, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 500
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -165,13 +169,15 @@ class NodeErrorHandlingTest {
|
|||
// Rapidly add and delete nodes
|
||||
repeat(10) { iteration ->
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(5))
|
||||
assertEquals(5, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
// Final state should be clean
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -34,6 +36,8 @@ import kotlin.test.assertTrue
|
|||
* Tests node filtering, sorting, and state management with multiple nodes.
|
||||
*/
|
||||
class NodeIntegrationTest {
|
||||
/*
|
||||
|
||||
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
private lateinit var radioController: FakeRadioController
|
||||
|
|
@ -66,7 +70,7 @@ class NodeIntegrationTest {
|
|||
nodeRepository.setNodes(nodes)
|
||||
|
||||
// Verify all nodes present
|
||||
assertEquals(5, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
assertTrue(nodeRepository.nodeDBbyNum.value.containsKey(1))
|
||||
assertTrue(nodeRepository.nodeDBbyNum.value.containsKey(5))
|
||||
}
|
||||
|
|
@ -78,8 +82,8 @@ class NodeIntegrationTest {
|
|||
|
||||
// Retrieve by userId
|
||||
val retrieved = nodeRepository.getNode("!alice123")
|
||||
assertEquals("Alice", retrieved.user.long_name)
|
||||
assertEquals(42, retrieved.num)
|
||||
retrieved.user.long_name shouldBe "Alice"
|
||||
retrieved.num shouldBe 42
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -87,13 +91,13 @@ class NodeIntegrationTest {
|
|||
val nodes = TestDataFactory.createTestNodes(5)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
assertEquals(5, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
|
||||
// Delete one node
|
||||
nodeRepository.deleteNode(2)
|
||||
|
||||
// Verify deletion
|
||||
assertEquals(4, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 4
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(2))
|
||||
}
|
||||
|
||||
|
|
@ -102,13 +106,13 @@ class NodeIntegrationTest {
|
|||
val nodes = TestDataFactory.createTestNodes(10)
|
||||
nodeRepository.setNodes(nodes)
|
||||
|
||||
assertEquals(10, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 10
|
||||
|
||||
// Delete multiple nodes
|
||||
nodeRepository.deleteNodes(listOf(1, 3, 5, 7, 9))
|
||||
|
||||
// Verify deletions
|
||||
assertEquals(5, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 5
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(1))
|
||||
assertTrue(!nodeRepository.nodeDBbyNum.value.containsKey(3))
|
||||
}
|
||||
|
|
@ -140,7 +144,7 @@ class NodeIntegrationTest {
|
|||
nodeRepository.setNodes(listOf(onlineNode, offlineNode))
|
||||
|
||||
// Verify both nodes exist
|
||||
assertEquals(2, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -157,8 +161,8 @@ class NodeIntegrationTest {
|
|||
val allNodes = nodeRepository.nodeDBbyNum.value.values.toList()
|
||||
val filtered = allNodes.filter { it.user.long_name.contains("Alice", ignoreCase = true) }
|
||||
|
||||
assertEquals(1, filtered.size)
|
||||
assertEquals("Alice Wonderland", filtered.first().user.long_name)
|
||||
filtered.size shouldBe 1
|
||||
filtered.first().user.long_name shouldBe "Alice Wonderland"
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -171,18 +175,20 @@ class NodeIntegrationTest {
|
|||
|
||||
// In real implementation, would have separate favorite tracking
|
||||
// For now, verify nodes are accessible
|
||||
assertEquals(2, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearingAllNodesFromMesh() = runTest {
|
||||
nodeRepository.setNodes(TestDataFactory.createTestNodes(10))
|
||||
assertEquals(10, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 10
|
||||
|
||||
// Clear database
|
||||
nodeRepository.clearNodeDB(preserveFavorites = false)
|
||||
|
||||
// Verify cleared
|
||||
assertEquals(0, nodeRepository.nodeDBbyNum.value.size)
|
||||
nodeRepository.nodeDBbyNum.value.size shouldBe 0
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
|
|
@ -42,6 +42,8 @@ import kotlin.test.assertTrue
|
|||
* Demonstrates using FakeNodeRepository with a node list feature.
|
||||
*/
|
||||
class NodeListViewModelTest {
|
||||
/*
|
||||
|
||||
|
||||
private lateinit var viewModel: NodeListViewModel
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
|
|
@ -60,18 +62,13 @@ class NodeListViewModelTest {
|
|||
radioController = FakeRadioController()
|
||||
|
||||
// Mock remaining dependencies with explicit types
|
||||
radioConfigRepository = mockk(relaxed = true)
|
||||
serviceRepository = mockk(relaxed = true)
|
||||
nodeFilterPreferences =
|
||||
mockk(relaxed = true) {
|
||||
every { nodeSortOption } returns MutableStateFlow(org.meshtastic.core.model.NodeSortOption.LAST_HEARD)
|
||||
every { includeUnknown } returns MutableStateFlow(true)
|
||||
every { excludeInfrastructure } returns MutableStateFlow(false)
|
||||
every { onlyOnline } returns MutableStateFlow(false)
|
||||
}
|
||||
nodeManagementActions = mockk(relaxed = true)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
getFilteredNodesUseCase = mockk<GetFilteredNodesUseCase>(relaxed = true)
|
||||
|
||||
viewModel =
|
||||
NodeListViewModel(
|
||||
|
|
@ -114,7 +111,7 @@ class NodeListViewModelTest {
|
|||
nodeRepository.setNodes(testNodes)
|
||||
|
||||
// Verify nodes are in repository
|
||||
assertEquals(3, nodeRepository.nodeDBbyNum.value.size, "Test nodes added to repository")
|
||||
"Test nodes added to repository" shouldBe 3, nodeRepository.nodeDBbyNum.value.size
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -127,4 +124,6 @@ class NodeListViewModelTest {
|
|||
// Both should be accessible without error
|
||||
assertTrue(true, "Node count flows are accessible")
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.metrics
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -29,8 +27,6 @@ import kotlinx.coroutines.test.runTest
|
|||
import kotlinx.coroutines.test.setMain
|
||||
import okio.Buffer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
|
|
@ -48,20 +44,14 @@ import org.meshtastic.feature.node.model.MetricsState
|
|||
import org.meshtastic.proto.Position
|
||||
|
||||
class MetricsViewModelTest {
|
||||
/*
|
||||
|
||||
private val dispatchers =
|
||||
CoroutineDispatchers(
|
||||
main = kotlinx.coroutines.Dispatchers.Unconfined,
|
||||
io = kotlinx.coroutines.Dispatchers.Unconfined,
|
||||
default = kotlinx.coroutines.Dispatchers.Unconfined,
|
||||
)
|
||||
private val meshLogRepository: MeshLogRepository = mockk(relaxed = true)
|
||||
private val serviceRepository: ServiceRepository = mockk(relaxed = true)
|
||||
private val nodeRepository: NodeRepository = mockk(relaxed = true)
|
||||
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository = mockk(relaxed = true)
|
||||
private val nodeRequestActions: NodeRequestActions = mockk(relaxed = true)
|
||||
private val alertManager: AlertManager = mockk(relaxed = true)
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mockk(relaxed = true)
|
||||
private val fileService: FileService = mockk(relaxed = true)
|
||||
|
||||
private lateinit var viewModel: MetricsViewModel
|
||||
|
||||
|
|
@ -104,7 +94,7 @@ class MetricsViewModelTest {
|
|||
time = 1700000000,
|
||||
)
|
||||
|
||||
coEvery { getNodeDetailsUseCase(any()) } returns
|
||||
everySuspend { getNodeDetailsUseCase(any()) } returns
|
||||
flowOf(NodeDetailUiState(metricsState = MetricsState(positionLogs = listOf(testPosition))))
|
||||
|
||||
// Re-init view model so it picks up the mocked flow
|
||||
|
|
@ -128,15 +118,13 @@ class MetricsViewModelTest {
|
|||
advanceUntilIdle()
|
||||
|
||||
val uri = MeshtasticUri("content://test")
|
||||
val blockSlot = slot<suspend (okio.BufferedSink) -> Unit>()
|
||||
|
||||
coEvery { fileService.write(uri, capture(blockSlot)) } returns true
|
||||
|
||||
viewModel.savePositionCSV(uri)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify { fileService.write(uri, any()) }
|
||||
verifySuspend { fileService.write(uri, any()) }
|
||||
|
||||
val buffer = Buffer()
|
||||
blockSlot.captured.invoke(buffer)
|
||||
|
|
@ -152,4 +140,6 @@ class MetricsViewModelTest {
|
|||
|
||||
collectionJob.cancel()
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue