10

I'm trying to test this one function in my application repository class which performs a database insertion. I'm using Koin as my dependency injection library. To do the testing I need to create a Database version that gets built in memory. To create that database I need the Android application context. So I created my test class like below.

import android.content.Context
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import com.chathuranga.shan.mycontacts.di.applicationModule
import com.chathuranga.shan.mycontacts.di.repositoryModule
import com.chathuranga.shan.mycontacts.room.AppDatabase
import org.junit.After
import org.junit.Test

import org.junit.Before
import org.junit.Rule
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.loadKoinModules
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.inject
import org.mockito.MockitoAnnotations

class ContactRepositoryTest : KoinTest {

    private val contactRepository: ContactRepository by inject()
    private lateinit var appDatabase: AppDatabase

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @Before
    fun setUp() {

        startKoin {
            printLogger()
            modules(listOf(applicationModule,repositoryModule))
        }

        MockitoAnnotations.initMocks(this)

        val context = ApplicationProvider.getApplicationContext<Context>()
        //val instrumentationContext = InstrumentationRegistry.getInstrumentation().targetContext

        appDatabase = Room
            .inMemoryDatabaseBuilder(context, AppDatabase::class.java)
            .allowMainThreadQueries()
            .build()

        loadKoinModules(module { single(override = true) { appDatabase } })
    }

    @Test
    fun insertContact() {

        val firstName = "Chathuranga"
        val secondName = "Shan"
        val phone = "07711247890"

        contactRepository.insertContact(firstName,secondName,phone,null,null)

    }

    @After
    fun tearDown() {
        stopKoin()
    }
}

And I'm getting this exception.

java.lang.IllegalStateException: No instrumentation registered! Must run under registering instrumentation.

As you can see I tried two ways to get Context (check the commented line in above class) and both failed the same way. And error pointed to the place where I create Context object. This test class in test folder not in androidTest folder. I want to separate this function and other functions that gonna come in the future and test in this class.

Here are my dependencies

//Testing
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.21.0"
testImplementation 'android.arch.core:core-testing:1.1.1'
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.koin:koin-test:2.0.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

My dependency injection class.

val applicationModule = module {
    single { AppDatabase.getDatabaseInstance(androidContext().applicationContext) }
}

val activityModule = module {

    scope(named<MainActivity>()) {
        scoped { (activity: MainActivity) ->
            Navigation
                .findNavController(activity, R.id.hostFragment)
        }
    }
}

I'm new to testing. If the way I have grasped the concept of unit testing please point it out. Otherwise Point out the problem with this code. Thanks.

1 Answers1

8

Create Context with Robolectric in a local unit test

For local unit tests run on the JVM, Robolectric works. If this is in an instrumented test, then Robolectric is not needed. InstrumentationRegistry can be used as in Joe Birch's sample.

1. Add libraries

JUnit 5

build.gradle

testImplementation "org.robolectric:robolectric:X.X.X"
testImplementation "androidx.test.ext:junit:X.X.X"

Junit 4

Built-in use with JUnit 4 and AndroidX library.

2. Create app Context with ApplicationProvider

Junit 5

See: Build local unit tests > Include framework dependencies - Android documentation

SomeTest.kt

import androidx.test.core.app.ApplicationProvider

@RunWith(RobolectricTestRunner::class)
class SomeTest {

    @Test
    fun someTest() {
        val appContext = ApplicationProvider.getApplicationContext<Context>()
        ...
    }

}


JUnit 4

See: Robolectric's documentation

3. Handle SDK 29/Java 9 error with class annotation @Config

See: How to add Java 9 to Android Studio? - StackOverflow

AdamHurwitz
  • 9,758
  • 10
  • 72
  • 134
  • 1
    I'm really thankful for this detailed answer. I'll try this out – Chathuranga Shan Jayarathna Jun 20 '20 at 14:00
  • 2
    Why not use `InstrumentationRegistry.getInstrumentation().context`? – IgorGanapolsky Oct 18 '20 at 16:18
  • That seems promising as well per Joe Birch's sample linked above. – AdamHurwitz Oct 18 '20 at 18:01
  • Is the reference to **JUnit5** erroneous or did you really get to use Robolectric+JUnit5? I wasted a lot of time trying to use Robolectric + JUnit5 but without success, and according to other answers this is not possible, see https://stackoverflow.com/a/51566798/13303234 and the issue linked there. All the links in your paragraphs titled "JUnit5" actually refer to documentation on JUnit4, and nothing in the code points to JUnit5. – Mabsten Jan 24 '21 at 14:19