使用 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)
}
}
TestApplication
class。我已验证此测试 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
。
我一直在尝试使用 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)
}
}
TestApplication
class。我已验证此测试 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)
然后在您的测试方法中,您可以通过
手动启动您的activityactivityRule.launchActivity(null)
此外,您可能想查看 https://mockk.io/ 进行模拟。您不必将 类 声明为 open
。