2

I am trying to test my viewmodel, which uses workmanagers to get data. Inside these workmanagers, a room database is used to insert the data and then later get the data. My problem is, that I am always getting the above error:

Error

Exception in thread "pool-3-thread-1" java.lang.IllegalStateException: Illegal connection pointer 1. Current pointers for thread Thread[pool-3-thread-1,5,SDK 30] []
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.getConnection(ShadowSQLiteConnection.java:386)
    at org.robolectric.shadows.ShadowSQLiteConnection$Connections.prepareStatement(ShadowSQLiteConnection.java:445)
    at org.robolectric.shadows.ShadowSQLiteConnection.nativePrepareStatement(ShadowSQLiteConnection.java:92)
    at android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
    at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1045)
    at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:652)
    at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:590)
    at android.database.sqlite.SQLiteProgram.__constructor__(SQLiteProgram.java:61)
    at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java)
    at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java)
    at android.database.sqlite.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1223)
    at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.compileStatement(FrameworkSQLiteDatabase.java:64)
    at androidx.room.InvalidationTracker.internalInit(InvalidationTracker.java:201)
    at androidx.room.RoomDatabase.internalInitInvalidationTracker(RoomDatabase.java:598)
    at androidx.work.impl.WorkDatabase_Impl.access$700(WorkDatabase_Impl.java:42)
    at androidx.work.impl.WorkDatabase_Impl$1.onOpen(WorkDatabase_Impl.java:110)
    at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:136)
    at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:195)
    at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:427)
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
    at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
    at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
    at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:622)
    at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:399)
    at androidx.work.impl.model.SystemIdInfoDao_Impl.getWorkSpecIds(SystemIdInfoDao_Impl.java:120)
    at androidx.work.impl.background.systemjob.SystemJobScheduler.reconcileJobs(SystemJobScheduler.java:284)
    at androidx.work.impl.utils.ForceStopRunnable.cleanUp(ForceStopRunnable.java:199)
    at androidx.work.impl.utils.ForceStopRunnable.forceStopRunnable(ForceStopRunnable.java:165)
    at androidx.work.impl.utils.ForceStopRunnable.run(ForceStopRunnable.java:102)
    at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

java.lang.NullPointerException
    at com.example.app.presentation.service.core.BaseServiceViewModel.getServicePrice(BaseServiceViewModel.kt:36)
    at com.example.app.presentation.service.core.BaseServiceViewModel.checkBatteryCard(BaseServiceViewModel.kt:90)
    at com.example.app.viewmodel.EmailViewModelTest.price should be added and deleted when battery is checked and unchecked(EmailViewModelTest.kt:217)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:570)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:278)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Test (Simplified)

@RunWith(RobolectricTestRunner::class)
@Config(maxSdk = Build.VERSION_CODES.P, minSdk = Build.VERSION_CODES.P)
class EmailViewModelTest {
    @get:Rule var instantExecutorRule = InstantTaskExecutorRule()

    private val context = InstrumentationRegistry.getInstrumentation().targetContext

    // Mocking Room Dao to use it inside ServiceWorkerContractFake
    @Mock private lateinit var dao: ServicePricingDao

    private lateinit var fakeWorkerContract: ServiceWorkerContractFake

    private lateinit var fakeServicePricing: ServicePricing
    private lateinit var viewModel: CalibrateRepairViewModel

    @Before
    fun setUp() {
        MockitoAnnotations.openMocks(this)

        fakeWorkerContract = ServiceWorkerContractFake(
            context,
            workWatcher = TestWorkwatcher(),
            servicePricingDao = dao,
        )

        viewModel = CalibrateRepairViewModel(
            // simplified
        )

        TestCoroutineDispatcher().runBlockingTest {
            val nEntity = fakeEmailRepository.getCalibratePrice("")
            fakeServicePricing = nEntity.toServicePricingCacheEntity().toServicePricing()
        }
    }

    @Test
    fun `price should be added when battery is checked`() {
        viewModel.checkBatteryCard() // getting error here
    }

Viewmodel

abstract class BaseServiceViewModel constructor(
    // Simplified dependencies
) : ViewModel() {

    private var _servicePrice: ServicePricing? = null
    val servicePrice get() = _servicePrice!!

    fun setStateEvent(emailStateEvent: ServiceStateEvent) {
        when (emailStateEvent) {
            is ServiceStateEvent.GetPrice -> {
                serviceNetworkInteractor.getServicePrice()
            }
        }
    }

    fun checkBatteryCard() {
        serviceNetworkInteractor.priceValidator.addDeleteGenericPrice(
            servicePrice.servicePricing.changeBatteryPrice, // NullPointerException
            serviceStateholder.batteryWanted.value
        )
    }

    init {
        setStateEvent(ServiceStateEvent.GetPrice)

        // Getting Data from database and setting _servicePrice
        viewModelScope.launch {
            servicePriceState.asFlow().collect {
                  val price = it.data.toServicePricing()

                  // Init price inside viewmodel (bad pratice, changed in compose)
                   _servicePrice = price
                }
            }
        }
    }
}

FakeServiceWorkerContract (Interactor, using workmanager here)

class ServiceWorkerContractFake(
    context: Context,
    servicePricingDao: ServicePricingDao,
    private val workWatcher: TestWorkwatcher
) : IWorkerContract<@JvmSuppressWildcards ServicePricingCacheEntity, @JvmSuppressWildcards String>(
    context,
    mWorkWatcher = workWatcher,
    mUniqueWorkName = "Test",
    mInputString = "TEST"
) {

    // Using fake Workmanager
    override val workRequest: OneTimeWorkRequest =
        OneTimeWorkRequestBuilder<CalibratePriceTestWorker>().build()

    override val workInfo: LiveData<Status<Unit>> =
        WorkManager.getInstance(context)
            .getWorkInfoByIdLiveData(workRequest.id)
            .mapWorkInfoToState()

    override val downloadedData: LiveData<ServicePricingCacheEntity> =
        servicePricingDao.get()

    override val _data: MutableLiveData<Status<ServicePricingCacheEntity>>
        get() = (workInfo to downloadedData) mapWith workWatcher
}

CalibratePriceTestWorker (fake Workmanager inside FakeServiceWorkerContract

class CalibratePriceTestWorker(
    context: Context,
    workerParams: WorkerParameters,
    private val testRepository: EmailRepository,
    private val testServiceDao: ServicePricingDao,
) : CoroutineWorker(context, workerParams) {
    override suspend fun doWork(): Result {
        val price = testRepository.getCalibratePrice("dummy")

        val priceCacheEntity = price.toServicePricingCacheEntity()

        testServiceDao.deleteAll() // not executed
        testServiceDao.insert(priceCacheEntity) // not executed

        return Result.success()
    }
}
Andrew
  • 4,264
  • 1
  • 21
  • 65

0 Answers0