测试 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