Migrating Junit4 tests to androidx: What causes 'delegate runner could not be loaded'?
AndroidJunitJunit4AndroidxAndroid Problem Overview
I am migrating my app to androidx, I can't seem to get my unit tests working. I took example from Google's AndroidJunitRunnerSample, which has been updated to use the new androidx api. I get the following error when trying to run my tests:
java.lang.Exception: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded. Check your build configuration.
Here is my module build.gradle:
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
// Test dependencies
androidTestImplementation 'androidx.test:core:1.0.0-beta02'
androidTestImplementation 'androidx.test.ext:junit:1.0.0-beta02'
androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
androidTestImplementation "androidx.arch.core:core-testing:2.0.0"
androidTestImplementation 'androidx.room:room-testing:2.1.0-alpha01'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
}
And here is how my tests are structured:
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
public class EntityParcelTest {
@BeforeClass
public void createEntities() {
// Setup...
}
@Test
void someTest() {
// Testing here
}
What am I doing wrong?
Android Solutions
Solution 1 - Android
Removing @RunWith(AndroidJUnit4.class)
annotations from the test classes fixed the issue, although I can't really say why or how it fixed it.
Edit: Allright I did some more testing. I migrated my app to Kotlin, and suddenly I noticed the tests began to work with the @RunWith
annotation, too. Here's what I found out:
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class) // <-- @RunWith + @BeforeClass = Error
public class AndroidXJunitTestJava {
@BeforeClass
public static void setup() {
// Setting up once before all tests
}
@Test
public void testing() {
// Testing....
}
}
This java test fails with the Delegate runner for AndroidJunit4 could not be loaded
error. But If I remove the @RunWith
annotation, it works. Also, if I replace the @BeforeClass
setup with just a @Before
, like this:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class) // <-- @RunWith + @Before = works?
public class AndroidXJunitTestJava {
@Before
public void setup() {
// Setting up before every test
}
@Test
public void testing() {
// Testing....
}
}
The tests will run without errors. I needed to use the @BeforeClass
annotation, so I just removed @RunWith
.
But now that I am using Kotlin, the following (which should be equal to the first java example) works:
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AndroidXJunitTest {
companion object {
@BeforeClass fun setup() {
// Setting up
}
}
@Test
fun testing() {
// Testing...
}
}
Also, as Alessandro Biessek said in an answer and @Ioane Sharvadze in the comments, the same error can happen with the @Rule
annotation. If I add a line
@Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
To the Kotlin example, the same delegate runner error happens. This must be replaced with
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
Explanation here.
Solution 2 - Android
You also get the error message if you use a test rule in Kotlin and write it Java style
@Rule
var mainActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)
You have to change @Rule
to @get:Rule
@get:Rule
var mainActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)
Solution 3 - Android
The error message displayed when I delete the @Test method.
You can try to put a @Test method and run
@Test
public void signInTest() {
}
Solution 4 - Android
While testing for activity make sure ActivityTestRule variable is public :
@Rule
public ActivityTestRule<YourActivity> activityTestRule = new ActivityTestRule<>(YourActivity.class);
Solution 5 - Android
There are 2 ways that you can try to resolve this problem:
Solution 1:
Build --> Clean Project. Then Build --> Rebuild Project
Solution 2:
There are 2 packages that has of AndroidJUnit4
androidx.test.runner
(deprecated)androidx.test.ext.junit.runners
Make sure your build.gradle (app) has this line
android {
defaultConfig {
....
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
....
}
}
and make sure you use (1) (which is deprecated) instead of (2) (new but not stable yet) in @RunWith(AndroidJUnit4.class)
before your test class.
Solution 6 - Android
Changing
@Test
void someTest() {
// Testing here
}
to
@Test
public void someTest() {
// Testing here
}
works for me.
Solution 7 - Android
The testing framework actually give too little information. I encountered the same issue and dug into the stack trace, and found these validation:
So you must declare your @BeforeClass
method static
.
Solution 8 - Android
You need to make sure that any class marked with the @BeforeClass annotation is public static . For example:
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
public class EntityParcelTest {
@BeforeClass
public static void createEntities() {
// Setup...
}
@Test
public void someTest() {
// Testing here
}
Solution 9 - Android
You can use @JvmField
. From documentation
> Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field
@Rule
@JvmField
val activityActivityTestRule = ActivityScenarioRule<MainActivity>(MainActivity::class.java)
Solution 10 - Android
If after adding the following dependencies:
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.1'
and
android{
defaultConfig{
...
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
...
}
}
doesn't help then use the deprecated AndroidJUnit4
class which belongs to the androidx.test.runner
package instead of the one belongs to androidx.test.ext.junit.runners.AndroidJUnit4
package.
I still don't understand why AndroidJUnit4
class is deprecated while the Gradle dependencies it belongs to is suggested by the Android team everywhere i.e Codelabs and docs
Solution 11 - Android
AndroidJUnit4 DOESN'T SUPPORT in the functions with @Test
annotations neither in the class nor in the superclass:
- the parameters:
@Test fun dontWork(param: String) {}
- private access modifiers:
@Test private fun dontWork2() {}
Note: Without @Test
annotations the above is allowed
An expected way could be:
ClassTest.kt
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ClassTest : SuperTest() {
@Test
fun test() {}
private fun allowedWithoutAnnotation(paramAllowed: String) {}
}
build.gradle (:mobile)
defaultConfig {
...
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
dependencies {
...
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
}
GL
Solution 12 - Android
In my case, I skipped annotate test case with @Test
. It is not your case. This answer is for others like me.
Solution 13 - Android
Maybe you haven't updated the runner on the gradle config file?
defaultConfig {
...
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Also, AndroidStudio 3.2 has an option to automate the migration of your dependencies to AndroidX (Refactor -> Migrate to AndroidX...) that did that for me.
Solution 14 - Android
For me the problem was the @Rule
annotation..
I don't know the cause for now.
As a temporary workaround yo can for example start your activity using UIAutomator for ActivityTestRule.
Solution 15 - Android
I gut this error for simply set fun as private, removing this solved this for me.
Solution 16 - Android
I fixed with this configuration:
My dependencies:
/*Instrumentation Test*/
androidTestImplementation "org.assertj:assertj-core:3.12.2"
androidTestImplementation ('androidx.test.espresso:espresso-core:3.2.0',{
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestImplementation "androidx.arch.core:core-testing:2.1.0-rc01"
In my defaultConfig
section I have:
defaultConfig {
applicationId "uy.edu.ude.archcomponents"
minSdkVersion 22
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
In the test I have:
package uy.edu.ude.archcomponents.repository
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.example.android.roomwordssample.WordDao
import com.example.android.roomwordssample.WordRoomDatabase
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import uy.edu.ude.archcomponents.entity.Word
import java.io.IOException
@RunWith(AndroidJUnit4::class)
class WordDaoTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var wordDao: WordDao
private lateinit var db: WordRoomDatabase
@Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().context
// Using an in-memory database because the information stored here disappears when the
// process is killed.
db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java)
// Allowing main thread queries, just for testing.
.allowMainThreadQueries()
.build()
wordDao = db.wordDao()
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
@Test
@Throws(Exception::class)
fun insertAndGetWord() {
runBlocking {
val word = Word("word")
wordDao.insert(word)
val allWords = wordDao.getAlphabetizedWords().waitForValue()
assertThat(allWords[0].word).isEqualTo(word.word)
}
}
}
I also use the android plugin version 3.4.2. I took the config from here
Solution 17 - Android
Some other possible reasons:
- Having only
@SmallTest
(or Medium-/Large-) test methods. Marking at least one method with@Test
solves the issues. setup()
/teardown()
methods are not public.
Solution 18 - Android
As for my case, defining a function parameter was the problem.
@Test
fun foo(string: String = "") { // Causes "Delegate runner could not be loaded" error.
// ...
}
I had to do something like the following.
@Test
fun foo() {
foo("")
}
fun foo(string: String) { // Can be called from other test functions.
// ...
}
Solution 19 - Android
In my case, I fixed it by explicitly declaring all @Test
, @Before
, and @After
methods aspublic
, and declaring @BeforeClass
and @AfterClass
as public static
If you leave any of those methods as implicitly/explicitly protected
, or explicitly private
; you'll encounter the following exception
java.lang.RuntimeException: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded.
So, the correct thing is to use
@Before
public void setUp() {
}
@BeforeClass
public static void init() {
}
Instead of:
@Before
void setUp() {
}
@Before
protected void setUp() {
}
@Before
private void setUp() {
}
@BeforeClass
public void init() {
}
Solution 20 - Android
After spending lots of time in one minor problem in jetpack compose UI testing I found that the issue is in composeTestRule variable that was it is private so Make sure that your composeTest variable should not be private.
@get:Rule
val composeTestRule = createAndroidComposeRule
NOT LIKE
@get:Rule
private val composeTestRule = createAndroidComposeRule
Solution 21 - Android
Another issue I was facing starting to using Espresso is the error
Failed to instantiate test runner class androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner site:stackoverflow.com
In this case I solved returning Unit to the fun
fun Example() : Unit {your code}
Bye bye