【问题标题】:How to mock reactive repository which returns Observable如何模拟返回 Observable 的反应式存储库
【发布时间】:2017-10-25 14:36:47
【问题描述】:

所以我有存储库,它为客户端提供 Observable。有没有办法可以模拟这个存储库,所以我不需要从我的模拟器发送位置或使用真实设备来获取一些位置?

界面如下:

interface RxLocationRepository {

@SuppressLint("MissingPermission")
fun onLocationUpdate(): Observable<Location>

fun stopLocationUpdates()
}

在我的客户端我这样使用:

class LocationManager(
    val rxLocationRepository: RxLocationRepository){

private fun appendGeoEvent(location: Location) {
    val locationGeoEvent = LocationGeoEvent(
            accuracy = location.accuracy.toDouble(),
            latitude = location.latitude,
            longitude = location.longitude,
            timestampGeoEvent = location.time
    )
    processGeoEvent(locationGeoEvent)
}

compositeDisposable.add(rxLocationRepository.onLocationUpdate()
            .subscribe(Consumer { location ->
                appendGeoEvent(location)
            }))
 ....

所以我将这个获得的位置发送到我的 appendGeoEvent 方法。

我可以使用例如 Mockito,但我不知道如何模拟这个存储库,所以我可以使用假位置。 另外,我想使用 Kotlin。

【问题讨论】:

  • 如何使用RxLocationRepository的依赖关系?
  • @tynn 我更新了我的问题,不知道你是什么意思。我使用 Dagger 将我的存储库注入到客户端
  • 啊,好的。所以我需要创建另一个模块,它只在我的测试包中?所以在 Dagger 中,我不能通过构造函数提供这个 Repository,而只是在测试中手动添加它,或者如何用 dagger 实现这个?在此之后,ESala 的答案似乎是合法的
  • @okset 我认为您不需要另一个 Dagger 模块。此外,您的 LocationManager 不使用存储库,因此您可以将其从构造函数中删除。无论如何,您需要考虑您真正测试的是什么。如果您正在测试LocationManager.appendGeoEvent(...) 方法,那么您甚至不需要存储库模拟,只需像我的答案一样创建一个测试位置并使用它来调用该方法。希望这会有所帮助;)
  • 它实际上使用这个存储库,我刚刚删除了“附加”功能,所以我只依赖从存储库传递的位置

标签: unit-testing mockito kotlin rx-java rx-java2


【解决方案1】:

如果使用 Mockito,你可以这样做:

import android.location.Location
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.junit.MockitoJUnitRunner
import java.util.*

@RunWith(MockitoJUnitRunner::class)
class LocationsTest{

    @Test
    fun should_return_one_location() {
        val location = Location("test").apply {
            latitude = 1.234
            longitude = 5.678
            // ...
        }
        val mockRepository = mock(RxLocationRepository::class.java)
        `when`(mockRepository.onLocationUpdate()).thenReturn(Observable.just(location))
        // use the mock
    }

}

我正在使用:testCompile "org.mockito:mockito-core:2.11.0"

【讨论】:

  • gradle 需要什么 mockito,才能像这样使用它?我正在使用 testImplementation "org.mockito:mockito-core:2.7.19",但无法识别
  • @okset 我已经用一个完整的例子更新了答案。不承认是什么意思?尝试重新构建项目。
  • 好的,我找到了我在androidTest中使用它的答案,而不是测试。所以: // 如果你想使用 Mockito 进行单元测试,则需要 testCompile 'org.mockito:mockito-core:2.7.22' // 如果你想使用 Mockito 进行 Android 测试,则需要 androidTestCompile 'org.mockito:mockito-android: 2.7.22'
  • @okset 如果答案有效,你能考虑接受吗?
  • 如果我想在测试中使用该存储库(相同的实例),答案会起作用,但存储库实际上是在不同的类中实现和关闭的,由 Dagger 提供。所以我不能像这样模拟它,但是我只是设置了新的 Dagger 模块,它将在不同的测试项目模块中为我提供这个仅用于测试的模拟实现。所以我想你的答案当然会起作用,如果你想测试 Dagger 没有提供的东西,因为我的存储库是嵌入的,不能像这样在测试中模拟它
【解决方案2】:

我需要设置不同的 Dagger 模块,它只是为我提供了这个存储库的实现——它返回不同的 Observable 流。

我是这样设置的。

@Module
abstract class StubLocationRepositoryModule {

@Binds
internal abstract fun bindsRxLocationRepository(stubRxLocationRepository: StubRxLocationRepository) : RxLocationRepository
}

我只是在组件的 androidTest 包中使用它。

所以我的实现是这样的:

只是一个例子:

class StubRxLocationRepository @Inject constructor(val stubLocationParameterName: String) : RxLocationRepository {

val location = Location("test").apply {
    latitude = 1.234
    longitude = 5.678
    accuracy = 20f
    time = Date().time
}

override fun onLocationUpdate(): Observable<Location> {
    Log.v("StubLocationRepository", "CHOSEN PARAMETER: $stubLocationParameterName")


    return when (stubLocationParameterName) {
        "highFreq" -> Observable.interval(50, TimeUnit.MILLISECONDS)
                .flatMap(
                        {
                            Observable.just(location)
                        }
                )
                .doOnNext{ t: Location -> Log.v("onLocationUpdate", t.toString()) }
        else -> Observable.interval(1, TimeUnit.SECONDS)
                .flatMap(
                        {
                            Observable.just(location)
                        }
                )
                .doOnNext{ t: Location -> Log.v("onLocationUpdate", t.toString()) }
    }
}

override fun stopLocationUpdates() {
    Log.v("StubLocationRepository", "stopLocationUpdates")
}
}

因此,在我的 Dagger 组件构建器中,我公开了一个方法,该方法将为我提供一些参数,我将在 StubRxLocation 实现中依赖这些参数 - 它会返回一些特定测试用例所需的流。

所以组件:

@Singleton
@Component(modules = arrayOf(StubLocationRepositoryModule::class, 
LocationTestInstrumentalModule::class, StubRssiRepositoryModule::class))
interface LocationTestInstrumentalComponent{
fun locationClient(): LocationClient


@Component.Builder
interface Builder {
    @BindsInstance
    fun context(context: Context): Builder
    @BindsInstance
    fun stubLocationRepositoryParameter(stubLocationRepositoryParameter: String): Builder
    fun build(): LocationTestInstrumentalComponent
}

}

所以在每个测试中,我都可以像这样带上模拟的存储库,这将准备好用于该测试用例:

@Test
fun someTest(){
       val component = DaggerLocationTestInstrumentalComponent.builder().stubLocationRepositoryParameter("highFreq").context(InstrumentationRegistry.getContext()).build()

val client = component.locationClient()
//i can expose some other methods, not only this 'locationClient' in this Component to return me some classes, like this RxLocationRepository(which will behave as i want) and others
}

【讨论】:

    猜你喜欢
    • 2021-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-23
    • 1970-01-01
    • 2021-05-08
    • 2011-05-27
    相关资源
    最近更新 更多