使用 Mock ViewModel 的 NoBeanDefFoundException,使用 Koin、Espresso 进行测试

NoBeanDefFoundException with Mock ViewModel, testing with Koin, Espresso

我一直在尝试使用 Koin 作为 DI 工具来获得一个简单的 Espresso 单元测试。这是我在 build.gradle

中使用的依赖项
    // testing with Koin
    // because of this
    // https://github.com/InsertKoinIO/koin/pull/604/commits/69391bc378bbb9007b9d82c46537e7d753be7ea3
    androidTestImplementation 'org.mockito:mockito-android:3.1.0'
    androidTestImplementation ("org.koin:koin-test:$koin_version") {
        exclude group: 'org.mockito'
    }

    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    // stuff like ActivityTestRule
    androidTestImplementation 'androidx.test:rules:1.2.0'
    // AndroidJUnit4
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    // test runner
    androidTestImplementation 'androidx.test:runner:1.2.0'

我的ViewModel声明

open class LoginViewModel(private val apiService: MockApiService) : ViewModel() {
..
..
}

这是它如何注入 Activity

private val loginViewModel: LoginViewModel by viewModel()

我的自定义 TestRunner 以便实例化自定义 TestApplication

class MyTestRunner : AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
        return super.newApplication(cl, TestApplication::class.java.name, context)
    }
}

TestApplicationclass。我已验证此测试 class 在调用测试时已初始化

class TestApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidLogger()
            androidContext(this@TestApplication)
            modules(emptyList())
        }
    }
}

这是我的实际 androidTest。一旦 activity 以 NoBeanDefFoundException

开始,这就会失败

No definition found for 'com.abhishek.mvvmdemo.onboarding.LoginViewModel' has been found.

@RunWith(AndroidJUnit4::class)
@LargeTest
class LoginActivityTest : KoinTest {
    private lateinit var loginViewModel: LoginViewModel

    @get:Rule
    val activityRule = ActivityTestRule(LoginActivity::class.java)

    @Before
    fun beforeTest() {
        loginViewModel = declareMock()
        loadKoinModules(
            module {
//                single { ApiModule.providesApiService() }
                viewModel { loginViewModel }
            }
        )
    }

    @Test
    fun testProgress() {
        activityRule.launchActivity(null)
        onView(withId(R.id.emailEt))
            .perform(ViewActions.typeText("abhishek"))
    }

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

我尝试了很多排列组合,但都没有成功。我的 gradle

中也恰好有以下配置

testOptions {
        animationsDisabled = true
    }

    packagingOptions {
        pickFirst 'mockito-extensions/org.mockito.plugins.MockMaker'
    }

testInstrumentationRunner "com.abhishek.mvvmdemo.MyTestRunner"

TL;DR

这是重现问题的github sample

这里发生的事情是 ActivityTestRule 在您的 @Before 方法之前启动 activity,因此模拟没有机会被初始化。

来自官方documentation,

This rule provides functional testing of a single Activity. When launchActivity is set to true in the constructor, the Activity under test will be launched before each test annotated with Test and before methods annotated with Before, and it will be terminated after the test is completed and methods annotated with After are finished.

您应该指定您不想自动启动 activity,方法是使用此 constructor

ActivityTestRule (Class<T> activityClass, 
                boolean initialTouchMode, 
                boolean launchActivity)

然后在您的测试方法中,您可以通过

手动启动您的activity
activityRule.launchActivity(null)

此外,您可能想查看 https://mockk.io/ 进行模拟。您不必将 类 声明为 open