测试 Android Kotlin 应用程序 - Mockito 与 Dagger 注入 null
Testing Android Kotlin app - Mockito with Dagger injects null
我正在学习使用 Mockito 和 Robolectric 在 Android 上进行测试。我使用 Clean Architecture 在 Kotlin 中使用 RxJava 和 Dagger2 创建了非常简单的应用程序。在设备上一切正常,但我无法通过测试。这是我的 LoginPresenterTest:
@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class)
public class LoginPresenterTest {
private lateinit var loginPresenter: LoginPresenter
@Rule @JvmField
public val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var mockContext: Context
@Mock
private lateinit var mockLoginUseCase: LoginUseCase
@Mock
private lateinit var mockLoginView: LoginView
@Mock
private lateinit var mockCredentialsUseCase: GetCredentials
@Before
public fun setUp() {
loginPresenter = LoginPresenter(mockCredentialsUseCase, mockLoginUseCase)
loginPresenter.view = mockLoginView
}
@Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
}
}
LoginPresenter 构造函数:
class LoginPresenter @Inject constructor(@Named("getCredentials") val getCredentials: UseCase,
@Named("loginUseCase") val loginUseCase: LoginUseCase) : Presenter<LoginView>
在 loginPresenter.resume()
我有:
override fun resume() {
getCredentials.execute(GetCredentialsSubscriber() as DefaultSubscriber<in Any>)
}
最后,GetCredentials:
open class GetCredentials @Inject constructor(var userRepository: UserRepository,
threadExecutor: Executor,
postExecutionThread: PostExecutionThread):
UseCase(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(): Observable<Credentials> = userRepository.credentials()
}
问题是,GetCredentials
中的每个字段都是空的。我想我错过了一些东西(我从这个项目中获取了模式:https://github.com/android10/Android-CleanArchitecture),但我找不到它是什么。有谁知道这可能是什么原因造成的?
您正在使用 GetCredentials
(@Mock var mockCredentialsUseCase: GetCredentials
) 的模拟实例,这就是您在其字段中包含 null
的原因。模拟除了被测试的主要 class (LoginPresenter
) 之外的所有内容很少是一个好主意。一种思考方式是将依赖项划分为 peers and internals。我会将测试重写为:
inline fun <reified T:Any> mock() : T = Mockito.mock(T::class.java)
@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class)
public class LoginPresenterTest {
val mockContext:Context = mock()
val mockLoginView:LoginView = mock().apply {
given(this.context()).willReturn(mockContext)
}
val userRepository:UserRepository = mock() // or some in memory implementation
val credentials = GetCredentials(userRepository, testThreadExecutor, testPostThreadExecutor) // yes, let's use real GetCredentials implementation
val loginUseCase = LoginUseCase() // and a real LoginUseCase if possible
val loginPresenter = LoginPresenter(credentials, loginUseCase).apply {
view = mockLoginView
}
@Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
// do actual assertions as what should happen
}
}
像往常一样,您需要考虑要测试的内容。测试的范围不必局限于单个class。通常更容易想到您正在测试的功能而不是 classes(例如 BDD). Above all try to avoid tests like this - 在我看来,它作为回归测试增加的价值很小,但仍然阻碍重构。
PS。机器人加 helper functions for context
我正在学习使用 Mockito 和 Robolectric 在 Android 上进行测试。我使用 Clean Architecture 在 Kotlin 中使用 RxJava 和 Dagger2 创建了非常简单的应用程序。在设备上一切正常,但我无法通过测试。这是我的 LoginPresenterTest:
@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class)
public class LoginPresenterTest {
private lateinit var loginPresenter: LoginPresenter
@Rule @JvmField
public val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var mockContext: Context
@Mock
private lateinit var mockLoginUseCase: LoginUseCase
@Mock
private lateinit var mockLoginView: LoginView
@Mock
private lateinit var mockCredentialsUseCase: GetCredentials
@Before
public fun setUp() {
loginPresenter = LoginPresenter(mockCredentialsUseCase, mockLoginUseCase)
loginPresenter.view = mockLoginView
}
@Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
}
}
LoginPresenter 构造函数:
class LoginPresenter @Inject constructor(@Named("getCredentials") val getCredentials: UseCase,
@Named("loginUseCase") val loginUseCase: LoginUseCase) : Presenter<LoginView>
在 loginPresenter.resume()
我有:
override fun resume() {
getCredentials.execute(GetCredentialsSubscriber() as DefaultSubscriber<in Any>)
}
最后,GetCredentials:
open class GetCredentials @Inject constructor(var userRepository: UserRepository,
threadExecutor: Executor,
postExecutionThread: PostExecutionThread):
UseCase(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(): Observable<Credentials> = userRepository.credentials()
}
问题是,GetCredentials
中的每个字段都是空的。我想我错过了一些东西(我从这个项目中获取了模式:https://github.com/android10/Android-CleanArchitecture),但我找不到它是什么。有谁知道这可能是什么原因造成的?
您正在使用 GetCredentials
(@Mock var mockCredentialsUseCase: GetCredentials
) 的模拟实例,这就是您在其字段中包含 null
的原因。模拟除了被测试的主要 class (LoginPresenter
) 之外的所有内容很少是一个好主意。一种思考方式是将依赖项划分为 peers and internals。我会将测试重写为:
inline fun <reified T:Any> mock() : T = Mockito.mock(T::class.java)
@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class)
public class LoginPresenterTest {
val mockContext:Context = mock()
val mockLoginView:LoginView = mock().apply {
given(this.context()).willReturn(mockContext)
}
val userRepository:UserRepository = mock() // or some in memory implementation
val credentials = GetCredentials(userRepository, testThreadExecutor, testPostThreadExecutor) // yes, let's use real GetCredentials implementation
val loginUseCase = LoginUseCase() // and a real LoginUseCase if possible
val loginPresenter = LoginPresenter(credentials, loginUseCase).apply {
view = mockLoginView
}
@Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
// do actual assertions as what should happen
}
}
像往常一样,您需要考虑要测试的内容。测试的范围不必局限于单个class。通常更容易想到您正在测试的功能而不是 classes(例如 BDD). Above all try to avoid tests like this - 在我看来,它作为回归测试增加的价值很小,但仍然阻碍重构。
PS。机器人加 helper functions for context