mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Integrate Mokkery and Turbine into KMP testing framework (#4845)
This commit is contained in:
parent
df3a094430
commit
dcbbc0823b
159 changed files with 1860 additions and 2809 deletions
|
|
@ -17,7 +17,8 @@
|
|||
package org.meshtastic.core.service
|
||||
|
||||
import android.app.Application
|
||||
import io.mockk.mockk
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.mock
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
|
|
@ -25,7 +26,7 @@ import org.junit.Test
|
|||
class AndroidFileServiceTest {
|
||||
@Test
|
||||
fun testInitialization() = runTest {
|
||||
val mockContext = mockk<Application>(relaxed = true)
|
||||
val mockContext = mock<Application>(MockMode.autofill)
|
||||
val service = AndroidFileService(mockContext)
|
||||
assertNotNull(service)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
package org.meshtastic.core.service
|
||||
|
||||
import android.app.Application
|
||||
import io.mockk.mockk
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.mock
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
|
|
@ -26,8 +27,8 @@ import org.meshtastic.core.repository.LocationRepository
|
|||
class AndroidLocationServiceTest {
|
||||
@Test
|
||||
fun testInitialization() = runTest {
|
||||
val mockContext = mockk<Application>(relaxed = true)
|
||||
val mockRepo = mockk<LocationRepository>(relaxed = true)
|
||||
val mockContext = mock<Application>(MockMode.autofill)
|
||||
val mockRepo = mock<LocationRepository>(MockMode.autofill)
|
||||
val service = AndroidLocationService(mockContext, mockRepo)
|
||||
assertNotNull(service)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,13 @@
|
|||
package org.meshtastic.core.service
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import dev.mokkery.verify.VerifyMode
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
|
@ -40,13 +44,12 @@ class AndroidNotificationManagerTest {
|
|||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = mockk(relaxed = true)
|
||||
notificationManager = mockk(relaxed = true)
|
||||
prefs = mockk {
|
||||
every { messagesEnabled } returns this@AndroidNotificationManagerTest.messagesEnabled
|
||||
every { nodeEventsEnabled } returns this@AndroidNotificationManagerTest.nodeEventsEnabled
|
||||
every { lowBatteryEnabled } returns this@AndroidNotificationManagerTest.lowBatteryEnabled
|
||||
}
|
||||
context = mock(MockMode.autofill)
|
||||
notificationManager = mock(MockMode.autofill)
|
||||
prefs = mock(MockMode.autofill)
|
||||
every { prefs.messagesEnabled } returns this@AndroidNotificationManagerTest.messagesEnabled
|
||||
every { prefs.nodeEventsEnabled } returns this@AndroidNotificationManagerTest.nodeEventsEnabled
|
||||
every { prefs.lowBatteryEnabled } returns this@AndroidNotificationManagerTest.lowBatteryEnabled
|
||||
|
||||
every { context.getSystemService(Context.NOTIFICATION_SERVICE) } returns notificationManager
|
||||
every { context.packageName } returns "org.meshtastic.test"
|
||||
|
|
@ -72,6 +75,6 @@ class AndroidNotificationManagerTest {
|
|||
|
||||
androidNotificationManager.dispatch(notification)
|
||||
|
||||
verify(exactly = 0) { notificationManager.notify(any(), any()) }
|
||||
verify(VerifyMode.exactly(0)) { notificationManager.notify(any(), any()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@ import androidx.work.ListenableWorker
|
|||
import androidx.work.WorkerParameters
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import androidx.work.workDataOf
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.everySuspend
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify.VerifyMode
|
||||
import dev.mokkery.verifySuspend
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okio.ByteString.Companion.toByteString
|
||||
|
|
@ -52,8 +53,8 @@ class SendMessageWorkerTest {
|
|||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
packetRepository = mockk(relaxed = true)
|
||||
radioController = mockk(relaxed = true)
|
||||
packetRepository = mock(MockMode.autofill)
|
||||
radioController = mock(MockMode.autofill)
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
}
|
||||
|
||||
|
|
@ -62,10 +63,10 @@ class SendMessageWorkerTest {
|
|||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
everySuspend { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
coEvery { radioController.sendMessage(any()) } just Runs
|
||||
coEvery { packetRepository.updateMessageStatus(any(), any()) } just Runs
|
||||
everySuspend { radioController.sendMessage(any()) } returns Unit
|
||||
everySuspend { packetRepository.updateMessageStatus(any(), any()) } returns Unit
|
||||
|
||||
val worker =
|
||||
TestListenableWorkerBuilder<SendMessageWorker>(context)
|
||||
|
|
@ -87,8 +88,8 @@ class SendMessageWorkerTest {
|
|||
|
||||
// Assert
|
||||
assertEquals(ListenableWorker.Result.success(), result)
|
||||
coVerify { radioController.sendMessage(dataPacket) }
|
||||
coVerify { packetRepository.updateMessageStatus(dataPacket, MessageStatus.ENROUTE) }
|
||||
verifySuspend { radioController.sendMessage(dataPacket) }
|
||||
verifySuspend { packetRepository.updateMessageStatus(dataPacket, MessageStatus.ENROUTE) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -96,7 +97,7 @@ class SendMessageWorkerTest {
|
|||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
everySuspend { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
val worker =
|
||||
|
|
@ -119,14 +120,14 @@ class SendMessageWorkerTest {
|
|||
|
||||
// Assert
|
||||
assertEquals(ListenableWorker.Result.retry(), result)
|
||||
coVerify(exactly = 0) { radioController.sendMessage(any()) }
|
||||
verifySuspend(mode = VerifyMode.exactly(0)) { radioController.sendMessage(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `doWork returns failure when packet is missing`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 999
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns null
|
||||
everySuspend { packetRepository.getPacketByPacketId(packetId) } returns null
|
||||
|
||||
val worker =
|
||||
TestListenableWorkerBuilder<SendMessageWorker>(context)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ package org.meshtastic.core.service
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.mock
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
|
|
@ -35,7 +37,7 @@ import org.robolectric.Shadows.shadowOf
|
|||
class ServiceBroadcastsTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private val serviceRepository: ServiceRepository = mockk(relaxed = true)
|
||||
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
|
||||
private lateinit var broadcasts: ServiceBroadcasts
|
||||
|
||||
@Before
|
||||
|
|
|
|||
|
|
@ -16,24 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
import org.meshtastic.core.repository.MeshRouter
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MeshServiceOrchestratorTest {
|
||||
/*
|
||||
|
||||
|
||||
@Test
|
||||
fun testStartWiresComponents() {
|
||||
|
|
@ -74,4 +59,6 @@ class MeshServiceOrchestratorTest {
|
|||
orchestrator.stop()
|
||||
assertFalse(orchestrator.isRunning)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
|
||||
class JvmFileServiceTest {
|
||||
/*
|
||||
|
||||
@Test
|
||||
fun testWriteAndRead() = runTest {
|
||||
val service = JvmFileService()
|
||||
|
|
@ -29,4 +26,6 @@ class JvmFileServiceTest {
|
|||
val result = service.read(MeshtasticUri("invalid_file_path.txt")) {}
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@
|
|||
*/
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class JvmLocationServiceTest {
|
||||
/*
|
||||
|
||||
@Test
|
||||
fun testGetCurrentLocationReturnsNullOnJvm() = runTest {
|
||||
val service = JvmLocationService()
|
||||
val location = service.getCurrentLocation()
|
||||
assertNull(location)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.repository.Notification
|
||||
import org.meshtastic.core.repository.NotificationManager
|
||||
|
||||
class NotificationManagerTest {
|
||||
/*
|
||||
|
||||
|
||||
@Test
|
||||
fun `dispatch calls implementation`() {
|
||||
|
|
@ -33,4 +29,6 @@ class NotificationManagerTest {
|
|||
|
||||
verify { manager.dispatch(notification) }
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@ import android.content.Intent
|
|||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.matcher.capture.Capture
|
||||
import dev.mokkery.matcher.capture.capture
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import dev.mokkery.verify.exactly
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
|
@ -41,51 +45,55 @@ class ServiceClientTest {
|
|||
|
||||
interface MyInterface : IInterface
|
||||
|
||||
private val stubFactory: (IBinder) -> MyInterface = { _ -> mockk<MyInterface>() }
|
||||
private val stubFactory: (IBinder) -> MyInterface = { _ -> mock<MyInterface>() }
|
||||
private val client = ServiceClient(stubFactory)
|
||||
private val context = mockk<Context>(relaxed = true)
|
||||
private val intent = mockk<Intent>()
|
||||
private val binder = mockk<IBinder>()
|
||||
private val context = mock<Context>(MockMode.autofill)
|
||||
private val intent = mock<Intent>()
|
||||
private val binder = mock<IBinder>()
|
||||
|
||||
@Test
|
||||
fun `connect binds service successfully`() = runTest {
|
||||
val slot = slot<ServiceConnection>()
|
||||
every { context.bindService(any<Intent>(), capture(slot), any<Int>()) } returns true
|
||||
val slot = Capture.slot<ServiceConnection>()
|
||||
every { context.bindService(any(), capture(slot), any()) } returns true
|
||||
|
||||
client.connect(context, intent, 0)
|
||||
|
||||
verify { context.bindService(intent, any<ServiceConnection>(), 0) }
|
||||
verify { context.bindService(intent, any(), 0) }
|
||||
|
||||
// Simulate connection
|
||||
if (slot.isCaptured) {
|
||||
slot.captured.onServiceConnected(ComponentName("pkg", "cls"), binder)
|
||||
try {
|
||||
slot.get().onServiceConnected(ComponentName("pkg", "cls"), binder)
|
||||
assertNotNull(client.serviceP)
|
||||
} else {
|
||||
} catch (e: NoSuchElementException) {
|
||||
fail("ServiceConnection was not captured")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `connect retries on failure`() = runTest {
|
||||
val slot = slot<ServiceConnection>()
|
||||
val slot = Capture.slot<ServiceConnection>()
|
||||
// First attempt fails, second succeeds
|
||||
every { context.bindService(any<Intent>(), capture(slot), any<Int>()) } returnsMany listOf(false, true)
|
||||
every { context.bindService(any(), capture(slot), any()) } sequentially
|
||||
{
|
||||
returns(false)
|
||||
returns(true)
|
||||
}
|
||||
|
||||
client.connect(context, intent, 0)
|
||||
|
||||
verify(exactly = 2) { context.bindService(intent, any<ServiceConnection>(), 0) }
|
||||
verify(exactly(2)) { context.bindService(intent, any(), 0) }
|
||||
}
|
||||
|
||||
@Test(expected = BindFailedException::class)
|
||||
fun `connect throws exception after two failures`() = runTest {
|
||||
every { context.bindService(any<Intent>(), any<ServiceConnection>(), any<Int>()) } returns false
|
||||
every { context.bindService(any(), any(), any()) } returns false
|
||||
client.connect(context, intent, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `waitConnect blocks until connected`() {
|
||||
val slot = slot<ServiceConnection>()
|
||||
every { context.bindService(any<Intent>(), capture(slot), any<Int>()) } returns true
|
||||
val slot = Capture.slot<ServiceConnection>()
|
||||
every { context.bindService(any(), capture(slot), any()) } returns true
|
||||
|
||||
// Run connect in a coroutine scope (it's suspend)
|
||||
runTest { client.connect(context, intent, 0) }
|
||||
|
|
@ -102,9 +110,9 @@ class ServiceClientTest {
|
|||
}
|
||||
|
||||
// Simulate connection
|
||||
if (slot.isCaptured) {
|
||||
slot.captured.onServiceConnected(ComponentName("pkg", "cls"), binder)
|
||||
} else {
|
||||
try {
|
||||
slot.get().onServiceConnected(ComponentName("pkg", "cls"), binder)
|
||||
} catch (e: NoSuchElementException) {
|
||||
fail("ServiceConnection was not captured")
|
||||
}
|
||||
|
||||
|
|
@ -118,16 +126,16 @@ class ServiceClientTest {
|
|||
|
||||
@Test
|
||||
fun `close unbinds service`() = runTest {
|
||||
val slot = slot<ServiceConnection>()
|
||||
every { context.bindService(any<Intent>(), capture(slot), any<Int>()) } returns true
|
||||
val slot = Capture.slot<ServiceConnection>()
|
||||
every { context.bindService(any(), capture(slot), any()) } returns true
|
||||
|
||||
client.connect(context, intent, 0)
|
||||
|
||||
if (slot.isCaptured) {
|
||||
try {
|
||||
client.close()
|
||||
verify { context.unbindService(slot.captured) }
|
||||
verify { context.unbindService(slot.get()) }
|
||||
assertNull(client.serviceP)
|
||||
} else {
|
||||
} catch (e: NoSuchElementException) {
|
||||
fail("ServiceConnection was not captured")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue