From dfd307a69821f09ca5d80bc9c3286dabc7ca75da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Mon, 19 Jun 2023 12:58:18 +0200 Subject: [PATCH] Make mock test working --- profile_gls/build.gradle.kts | 9 +- .../no/nordicsemi/android/gls/GlsServer.kt | 49 ++++++---- .../gls/main/viewmodel/GLSViewModel.kt | 5 +- .../android/gls/GLSViewModelTest.kt | 95 +++++++++++-------- settings.gradle.kts | 2 +- 5 files changed, 97 insertions(+), 63 deletions(-) diff --git a/profile_gls/build.gradle.kts b/profile_gls/build.gradle.kts index 0a1dadb2..142d404d 100644 --- a/profile_gls/build.gradle.kts +++ b/profile_gls/build.gradle.kts @@ -66,12 +66,11 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.service) - testImplementation(libs.junit4) - - testImplementation("io.mockk:mockk:1.13.5") + testImplementation(libs.test.mockk) testImplementation(libs.androidx.test.ext) testImplementation(libs.kotlinx.coroutines.test) - testImplementation("org.slf4j:slf4j-simple:2.0.5") - testImplementation("org.robolectric:robolectric:4.10.3") + testImplementation(libs.test.slf4j.simple) + testImplementation(libs.test.robolectric) + testImplementation(libs.kotlin.junit) } diff --git a/profile_gls/src/debug/java/no/nordicsemi/android/gls/GlsServer.kt b/profile_gls/src/debug/java/no/nordicsemi/android/gls/GlsServer.kt index be3cc733..53128aa5 100644 --- a/profile_gls/src/debug/java/no/nordicsemi/android/gls/GlsServer.kt +++ b/profile_gls/src/debug/java/no/nordicsemi/android/gls/GlsServer.kt @@ -38,17 +38,24 @@ class GlsServer @Inject constructor( private val scope: CoroutineScope ) { + lateinit var server: BleGattServer + lateinit var glsCharacteristic: BleServerGattCharacteristic lateinit var glsContextCharacteristic: BleServerGattCharacteristic lateinit var racpCharacteristic: BleServerGattCharacteristic lateinit var batteryLevelCharacteristic: BleServerGattCharacteristic + private var lastRequest = byteArrayOf() + + val YOUNGEST_RECORD = byteArrayOf(0x07, 0x00, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x05, 0x00, 0x00, 0x26, 0xD2.toByte(), 0x11) + val OLDEST_RECORD = byteArrayOf(0x07, 0x04, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x11, 0x00, 0x00, 0x82.toByte(), 0xD2.toByte(), 0x11) + val records = listOf( - byteArrayOf(0x07, 0x00, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x05, 0x00, 0x00, 0x26, 0xD2.toByte(), 0x11), + YOUNGEST_RECORD, byteArrayOf(0x07, 0x01, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x08, 0x00, 0x00, 0x3D, 0xD2.toByte(), 0x11), byteArrayOf(0x07, 0x02, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x0B, 0x00, 0x00, 0x54, 0xD2.toByte(), 0x11), byteArrayOf(0x07, 0x03, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x0E, 0x00, 0x00, 0x6B, 0xD2.toByte(), 0x11), - byteArrayOf(0x07, 0x04, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x11, 0x00, 0x00, 0x82.toByte(), 0xD2.toByte(), 0x11) + OLDEST_RECORD ) val racp = byteArrayOf(0x06, 0x00, 0x01, 0x01) @@ -96,7 +103,7 @@ class GlsServer @Inject constructor( listOf(batteryLevelCharacteristic) ) - val server = BleGattServer.create( + server = BleGattServer.create( context = context, config = arrayOf(serviceConfig, batteryService), mock = device @@ -112,6 +119,10 @@ class GlsServer @Inject constructor( } } + internal fun stopServer() { + server.stopServer() + } + private fun setUpConnection(connection: BluetoothGattServerConnection) { val glsService = connection.services.findService(GLS_SERVICE_UUID)!! glsCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!! @@ -123,27 +134,33 @@ class GlsServer @Inject constructor( startGlsService(connection) - startBatteryService(connection) +// startBatteryService(connection) } private fun startGlsService(connection: BluetoothGattServerConnection) { racpCharacteristic.value .filter { it.isNotEmpty() } - .onEach { - if (it.contentEquals(RecordAccessControlPointInputParser.reportNumberOfAllStoredRecords().value)) { - sendAll(glsCharacteristic) - racpCharacteristic.setValue(racp) - } else if (it.contentEquals(RecordAccessControlPointInputParser.reportLastStoredRecord().value)) { - sendLast(glsCharacteristic) - racpCharacteristic.setValue(racp) - } else if (it.contentEquals(RecordAccessControlPointInputParser.reportFirstStoredRecord().value)) { - sendFirst(glsCharacteristic) - racpCharacteristic.setValue(racp) - } - } + .onEach { lastRequest = it } .launchIn(scope) } + internal fun continueWithResponse() { + sendResponse(lastRequest) + } + + private fun sendResponse(request: ByteArray) { + if (request.contentEquals(RecordAccessControlPointInputParser.reportNumberOfAllStoredRecords().value)) { + sendAll(glsCharacteristic) + racpCharacteristic.setValue(racp) + } else if (request.contentEquals(RecordAccessControlPointInputParser.reportLastStoredRecord().value)) { + sendLast(glsCharacteristic) + racpCharacteristic.setValue(racp) + } else if (request.contentEquals(RecordAccessControlPointInputParser.reportFirstStoredRecord().value)) { + sendFirst(glsCharacteristic) + racpCharacteristic.setValue(racp) + } + } + private fun sendFirst(characteristics: BleServerGattCharacteristic) { characteristics.setValue(records.first()) } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt index a8eddd6b..b9b6235d 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.launch import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.ProfileConnectedEvent +import no.nordicsemi.android.common.logger.BlekLoggerAndLauncher import no.nordicsemi.android.common.navigation.NavigationResult import no.nordicsemi.android.common.navigation.Navigator import no.nordicsemi.android.gls.GlsDetailsDestinationId @@ -67,9 +68,6 @@ import no.nordicsemi.android.kotlin.ble.client.main.service.BleGattServices import no.nordicsemi.android.kotlin.ble.core.ServerDevice import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus -import no.nordicsemi.android.common.logger.BlekLogger -import no.nordicsemi.android.common.logger.BlekLoggerAndLauncher -import no.nordicsemi.android.kotlin.ble.core.ext.toDisplayString import no.nordicsemi.android.kotlin.ble.profile.battery.BatteryLevelParser import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementContextParser import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementParser @@ -239,7 +237,6 @@ internal class GLSViewModel @Inject constructor( } private fun onAccessControlPointDataReceived(data: RecordAccessControlPointData) = viewModelScope.launch { - println("AAA: Response code: ${data}") when (data) { is NumberOfRecordsData -> onNumberOfRecordsReceived(data.numberOfRecords) is ResponseData -> when (data.responseCode) { diff --git a/profile_gls/src/test/java/no/nordicsemi/android/gls/GLSViewModelTest.kt b/profile_gls/src/test/java/no/nordicsemi/android/gls/GLSViewModelTest.kt index 9e8866b7..c2eb1b56 100644 --- a/profile_gls/src/test/java/no/nordicsemi/android/gls/GLSViewModelTest.kt +++ b/profile_gls/src/test/java/no/nordicsemi/android/gls/GLSViewModelTest.kt @@ -12,7 +12,6 @@ import io.mockk.mockkStatic import io.mockk.spyk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -28,19 +27,21 @@ import no.nordicsemi.android.gls.main.view.OnWorkingModeSelected import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel import no.nordicsemi.android.kotlin.ble.client.main.ClientScope import no.nordicsemi.android.kotlin.ble.core.MockServerDevice -import no.nordicsemi.android.kotlin.ble.core.ServerDevice import no.nordicsemi.android.kotlin.ble.core.data.BleGattConnectionStatus import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus +import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementParser import no.nordicsemi.android.kotlin.ble.profile.gls.data.RequestStatus import no.nordicsemi.android.kotlin.ble.server.main.ServerScope import no.nordicsemi.android.ui.view.NordicLoggerFactory import no.nordicsemi.android.ui.view.StringConst import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test +import kotlin.test.assertContentEquals /** * Example local unit test, which will execute on the development machine (host). @@ -93,6 +94,7 @@ internal class GLSViewModelTest { every { ClientScope } returns CoroutineScope(UnconfinedTestDispatcher()) mockkStatic("no.nordicsemi.android.kotlin.ble.server.main.ServerScopeKt") every { ServerScope } returns CoroutineScope(UnconfinedTestDispatcher()) + every { stringConst.APP_NAME } returns "Test" viewModel = spyk(GLSViewModel(context, navigator, analytics, stringConst, object : NordicLoggerFactory { @@ -106,6 +108,8 @@ internal class GLSViewModelTest { } })) + justRun { viewModel.logAnalytics(any()) } + glsServer = GlsServer(CoroutineScope(UnconfinedTestDispatcher())) glsServer.start(spyk(), device) } @@ -123,54 +127,71 @@ internal class GLSViewModelTest { } @Test - fun checkOnClick() = runTest { -// every { viewModel.recordAccessControlPointCharacteristic } returns characteristic -// coJustRun { characteristic.write(any(), any()) } - - viewModel.onEvent(OnWorkingModeSelected(WorkingMode.FIRST)) - - advanceUntilIdle() - assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus) - } - - @Test - fun `when connection fails return disconnected`() { - val serverDevice = mockk() + fun `when connection fails return disconnected`() = runTest { val disconnectedState = GattConnectionStateWithStatus( GattConnectionState.STATE_DISCONNECTED, BleGattConnectionStatus.SUCCESS ) -// every { viewModel.recordAccessControlPointCharacteristic } returns characteristic -// coJustRun { characteristic.write(any(), any()) } - mockkStatic("no.nordicsemi.android.kotlin.ble.client.main.ClientDeviceExtKt") - every { serverDevice.name } returns "Test" - every { serverDevice.address } returns "11:22:33:44:55" - every { stringConst.APP_NAME } returns "Test" + viewModel.handleResult(NavigationResult.Success(device)) + glsServer.stopServer() - viewModel.handleResult(NavigationResult.Success(serverDevice)) + advanceUntilIdle() assertEquals(disconnectedState, viewModel.state.value.glsServiceData.connectionState) } @Test - fun checkOnClick2() = runTest { - every { stringConst.APP_NAME } returns "Test" - justRun { viewModel.logAnalytics(any()) } - + fun `when request first record then change status and get 1 record`() = runTest { viewModel.handleResult(NavigationResult.Success(device)) + advanceUntilIdle() //Needed because of delay() in waitForBonding() + assertEquals(RequestStatus.IDLE, viewModel.state.value.glsServiceData.requestStatus) - advanceUntilIdle() - delay(1000) viewModel.onEvent(OnWorkingModeSelected(WorkingMode.FIRST)) + assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus) -// advanceUntilIdle() -// assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus) -// -//// glsServer.glsCharacteristic.setValue(glsServer.records.first()) -// glsServer.racpCharacteristic.setValue(glsServer.racp) -// -// advanceUntilIdle() -// -// assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus) + glsServer.continueWithResponse() //continue server breakpoint + + assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus) + assertEquals(1, viewModel.state.value.glsServiceData.records.size) + + val parsedResponse = GlucoseMeasurementParser.parse(glsServer.YOUNGEST_RECORD) + assertEquals(parsedResponse, viewModel.state.value.glsServiceData.records.keys.first()) + } + + @Test + fun `when request last record then change status and get 1 record`() = runTest { + viewModel.handleResult(NavigationResult.Success(device)) + advanceUntilIdle() //Needed because of delay() in waitForBonding() + assertEquals(RequestStatus.IDLE, viewModel.state.value.glsServiceData.requestStatus) + + viewModel.onEvent(OnWorkingModeSelected(WorkingMode.LAST)) + assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus) + + glsServer.continueWithResponse() //continue server breakpoint + + assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus) + assertEquals(1, viewModel.state.value.glsServiceData.records.size) + + val parsedResponse = GlucoseMeasurementParser.parse(glsServer.OLDEST_RECORD) + assertEquals(parsedResponse, viewModel.state.value.glsServiceData.records.keys.first()) + } + + @Test + fun `when request all record then change status and get 5 records`() = runTest { + viewModel.handleResult(NavigationResult.Success(device)) + advanceUntilIdle() //Needed because of delay() in waitForBonding() + assertEquals(RequestStatus.IDLE, viewModel.state.value.glsServiceData.requestStatus) + + viewModel.onEvent(OnWorkingModeSelected(WorkingMode.ALL)) + assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus) + + glsServer.continueWithResponse() //continue server breakpoint + advanceUntilIdle() //We have to use because of delay() in sendAll() + + assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus) + assertEquals(5, viewModel.state.value.glsServiceData.records.size) + + val expectedRecords = glsServer.records.map { GlucoseMeasurementParser.parse(it) } + assertContentEquals(expectedRecords, viewModel.state.value.glsServiceData.records.keys) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1175320e..771d7b99 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -50,7 +50,7 @@ dependencyResolutionManagement { } versionCatalogs { create("libs") { - from("no.nordicsemi.android.gradle:version-catalog:1.5.5") + from("no.nordicsemi.android.gradle:version-catalog:1.5.8") } } }