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()
}
}