Make mock test working

This commit is contained in:
Sylwester Zieliński
2023-06-19 12:58:18 +02:00
parent bcd481307c
commit dfd307a698
5 changed files with 97 additions and 63 deletions

View File

@@ -66,12 +66,11 @@ dependencies {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.service) implementation(libs.androidx.lifecycle.service)
testImplementation(libs.junit4) testImplementation(libs.junit4)
testImplementation(libs.test.mockk)
testImplementation("io.mockk:mockk:1.13.5")
testImplementation(libs.androidx.test.ext) testImplementation(libs.androidx.test.ext)
testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
testImplementation("org.slf4j:slf4j-simple:2.0.5") testImplementation(libs.test.slf4j.simple)
testImplementation("org.robolectric:robolectric:4.10.3") testImplementation(libs.test.robolectric)
testImplementation(libs.kotlin.junit)
} }

View File

@@ -38,17 +38,24 @@ class GlsServer @Inject constructor(
private val scope: CoroutineScope private val scope: CoroutineScope
) { ) {
lateinit var server: BleGattServer
lateinit var glsCharacteristic: BleServerGattCharacteristic lateinit var glsCharacteristic: BleServerGattCharacteristic
lateinit var glsContextCharacteristic: BleServerGattCharacteristic lateinit var glsContextCharacteristic: BleServerGattCharacteristic
lateinit var racpCharacteristic: BleServerGattCharacteristic lateinit var racpCharacteristic: BleServerGattCharacteristic
lateinit var batteryLevelCharacteristic: 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( 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, 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, 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, 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) val racp = byteArrayOf(0x06, 0x00, 0x01, 0x01)
@@ -96,7 +103,7 @@ class GlsServer @Inject constructor(
listOf(batteryLevelCharacteristic) listOf(batteryLevelCharacteristic)
) )
val server = BleGattServer.create( server = BleGattServer.create(
context = context, context = context,
config = arrayOf(serviceConfig, batteryService), config = arrayOf(serviceConfig, batteryService),
mock = device mock = device
@@ -112,6 +119,10 @@ class GlsServer @Inject constructor(
} }
} }
internal fun stopServer() {
server.stopServer()
}
private fun setUpConnection(connection: BluetoothGattServerConnection) { private fun setUpConnection(connection: BluetoothGattServerConnection) {
val glsService = connection.services.findService(GLS_SERVICE_UUID)!! val glsService = connection.services.findService(GLS_SERVICE_UUID)!!
glsCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!! glsCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!!
@@ -123,27 +134,33 @@ class GlsServer @Inject constructor(
startGlsService(connection) startGlsService(connection)
startBatteryService(connection) // startBatteryService(connection)
} }
private fun startGlsService(connection: BluetoothGattServerConnection) { private fun startGlsService(connection: BluetoothGattServerConnection) {
racpCharacteristic.value racpCharacteristic.value
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.onEach { .onEach { lastRequest = it }
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)
}
}
.launchIn(scope) .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) { private fun sendFirst(characteristics: BleServerGattCharacteristic) {
characteristics.setValue(records.first()) characteristics.setValue(records.first())
} }

View File

@@ -49,6 +49,7 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent 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.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.gls.GlsDetailsDestinationId 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.ServerDevice
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus 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.battery.BatteryLevelParser
import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementContextParser import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementContextParser
import no.nordicsemi.android.kotlin.ble.profile.gls.GlucoseMeasurementParser 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 { private fun onAccessControlPointDataReceived(data: RecordAccessControlPointData) = viewModelScope.launch {
println("AAA: Response code: ${data}")
when (data) { when (data) {
is NumberOfRecordsData -> onNumberOfRecordsReceived(data.numberOfRecords) is NumberOfRecordsData -> onNumberOfRecordsReceived(data.numberOfRecords)
is ResponseData -> when (data.responseCode) { is ResponseData -> when (data.responseCode) {

View File

@@ -12,7 +12,6 @@ import io.mockk.mockkStatic
import io.mockk.spyk import io.mockk.spyk
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle 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.gls.main.viewmodel.GLSViewModel
import no.nordicsemi.android.kotlin.ble.client.main.ClientScope import no.nordicsemi.android.kotlin.ble.client.main.ClientScope
import no.nordicsemi.android.kotlin.ble.core.MockServerDevice 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.BleGattConnectionStatus
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus 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.profile.gls.data.RequestStatus
import no.nordicsemi.android.kotlin.ble.server.main.ServerScope import no.nordicsemi.android.kotlin.ble.server.main.ServerScope
import no.nordicsemi.android.ui.view.NordicLoggerFactory import no.nordicsemi.android.ui.view.NordicLoggerFactory
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.test.assertContentEquals
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
@@ -93,6 +94,7 @@ internal class GLSViewModelTest {
every { ClientScope } returns CoroutineScope(UnconfinedTestDispatcher()) every { ClientScope } returns CoroutineScope(UnconfinedTestDispatcher())
mockkStatic("no.nordicsemi.android.kotlin.ble.server.main.ServerScopeKt") mockkStatic("no.nordicsemi.android.kotlin.ble.server.main.ServerScopeKt")
every { ServerScope } returns CoroutineScope(UnconfinedTestDispatcher()) every { ServerScope } returns CoroutineScope(UnconfinedTestDispatcher())
every { stringConst.APP_NAME } returns "Test"
viewModel = spyk(GLSViewModel(context, navigator, analytics, stringConst, object : viewModel = spyk(GLSViewModel(context, navigator, analytics, stringConst, object :
NordicLoggerFactory { NordicLoggerFactory {
@@ -106,6 +108,8 @@ internal class GLSViewModelTest {
} }
})) }))
justRun { viewModel.logAnalytics(any()) }
glsServer = GlsServer(CoroutineScope(UnconfinedTestDispatcher())) glsServer = GlsServer(CoroutineScope(UnconfinedTestDispatcher()))
glsServer.start(spyk(), device) glsServer.start(spyk(), device)
} }
@@ -123,54 +127,71 @@ internal class GLSViewModelTest {
} }
@Test @Test
fun checkOnClick() = runTest { fun `when connection fails return disconnected`() = 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<ServerDevice>()
val disconnectedState = GattConnectionStateWithStatus( val disconnectedState = GattConnectionStateWithStatus(
GattConnectionState.STATE_DISCONNECTED, GattConnectionState.STATE_DISCONNECTED,
BleGattConnectionStatus.SUCCESS BleGattConnectionStatus.SUCCESS
) )
// every { viewModel.recordAccessControlPointCharacteristic } returns characteristic viewModel.handleResult(NavigationResult.Success(device))
// coJustRun { characteristic.write(any(), any()) } glsServer.stopServer()
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(serverDevice)) advanceUntilIdle()
assertEquals(disconnectedState, viewModel.state.value.glsServiceData.connectionState) assertEquals(disconnectedState, viewModel.state.value.glsServiceData.connectionState)
} }
@Test @Test
fun checkOnClick2() = runTest { fun `when request first record then change status and get 1 record`() = runTest {
every { stringConst.APP_NAME } returns "Test"
justRun { viewModel.logAnalytics(any()) }
viewModel.handleResult(NavigationResult.Success(device)) 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)) viewModel.onEvent(OnWorkingModeSelected(WorkingMode.FIRST))
assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus)
// advanceUntilIdle() glsServer.continueWithResponse() //continue server breakpoint
// assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus)
// assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus)
//// glsServer.glsCharacteristic.setValue(glsServer.records.first()) assertEquals(1, viewModel.state.value.glsServiceData.records.size)
// glsServer.racpCharacteristic.setValue(glsServer.racp)
// val parsedResponse = GlucoseMeasurementParser.parse(glsServer.YOUNGEST_RECORD)
// advanceUntilIdle() assertEquals(parsedResponse, viewModel.state.value.glsServiceData.records.keys.first())
// }
// assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus)
@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)
} }
} }

View File

@@ -50,7 +50,7 @@ dependencyResolutionManagement {
} }
versionCatalogs { versionCatalogs {
create("libs") { create("libs") {
from("no.nordicsemi.android.gradle:version-catalog:1.5.5") from("no.nordicsemi.android.gradle:version-catalog:1.5.8")
} }
} }
} }