代码之家  ›  专栏  ›  技术社区  ›  Vlad

使用LiveData、RxJava/RxKotlin和Spek的Android测试中的片状

  •  0
  • Vlad  · 技术社区  · 5 年前

    设置:

    @PerFragment
    class SomeViewModel @Inject constructor(private val someActionUseCase: SomeActionUseCase,
                                            someUpdateStateUseCase: SomeUpdateStateUseCase) : ViewModel() {
    
        private val someState = MutableLiveData<SomeState>()
    
        private val stateSubscription: Disposable
    
        // region Lifecycle
        init {
            stateSubscription = someUpdateStateUseCase.state()
                    .subscribeIoObserveMain() // extension function
                    .subscribe { newState ->
                        someState.value = newState
                    })
        }
    
        override fun onCleared() {
            stateSubscription.dispose()
    
            super.onCleared()
        }
        // endregion
    
        // region Public Functions
        fun someState() = someState
    
        fun someAction(someValue: Boolean) {
            val someNewValue = if (someValue) "This" else "That"
    
            someActionUseCase.someAction(someNewValue)
        }
        // endregion
    }
    

    更新状态用例:

    @Singleton
    class UpdateSomeStateUseCase @Inject constructor(
                private var state: SomeState = initialState) {
    
        private val statePublisher: PublishProcessor<SomeState> = 
                PublishProcessor.create()
    
        fun update(state: SomeState) {
            this.state = state
    
            statePublisher.onNext(state)
        }
    
        fun state(): Observable<SomeState> = statePublisher.toObservable()
                                                           .startWith(state)
    }
    

    我们使用Spek进行单元测试。

    @RunWith(JUnitPlatform::class)
    class SomeViewModelTest : SubjectSpek<SomeViewModel>({
    
        setRxSchedulersTrampolineOnMain()
    
        var mockSomeActionUseCase = mock<SomeActionUseCase>()
        var mockSomeUpdateStateUseCase = mock<SomeUpdateStateUseCase>()
    
        var liveState = MutableLiveData<SomeState>()
    
        val initialState = SomeState(initialValue)
        val newState = SomeState(newValue)
    
        val behaviorSubject = BehaviorSubject.createDefault(initialState)
    
        subject {
            mockSomeActionUseCase = mock()
            mockSomeUpdateStateUseCase = mock()
    
            whenever(mockSomeUpdateStateUseCase.state()).thenReturn(behaviorSubject)
    
            SomeViewModel(mockSomeActionUseCase, mockSomeUpdateStateUseCase).apply {
                liveState = state() as MutableLiveData<SomeState>
            }
        }
    
        beforeGroup { setTestRxAndLiveData() }
        afterGroup { resetTestRxAndLiveData() }
    
        context("some screen") {
            given("the action to open the screen") {
                on("screen opened") {
                    subject
                    behaviorSubject.startWith(initialState)
    
                    it("displays the initial state") {
                        assertEquals(liveState.value, initialState)
                    }
                }
            }
    
            given("some setup") {
                on("some action") {
                    it("does something") {
                        subject.doSomething(someValue)
    
                        verify(mockSomeUpdateStateUseCase).someAction(someOtherValue)
                    }
                }
    
                on("action updating the state") {
                    it("displays new state") {
                        behaviorSubject.onNext(newState)
    
                        assertEquals(liveState.value, newState)
                    }
                }
            }
        }
    }
    

    起初,我们使用的是可观察对象,而不是行为主体:

    var observable = Observable.just(initialState)
    ...
    whenever(mockSomeUpdateStateUseCase.state()).thenReturn(observable)
    ...
    observable = Observable.just(newState)
    assertEquals(liveState.value, newState)
    

    val behaviorSubject = BehaviorSubject.createDefault(initialState)
    ...
    whenever(mockSomeUpdateStateUseCase.state()).thenReturn(behaviorSubject)
    ...
    behaviorSubject.onNext(newState)
    assertEquals(liveState.value, newState)
    

    但是单元测试是不可靠的。大多数情况下,他们会通过(总是在孤立运行时),但有时他们会失败时,运行整个套装。我们认为这与Rx的异步性质有关,我们将其转移到behaviorSubject,以便能够控制onNext()何时发生。当我们在本地机器上从AndroidStudio运行它们时,测试现在通过了,但是在构建机器上它们仍然不稳定。重新启动构建通常会使它们通过。

    失败的测试总是那些我们断言LiveData值的测试。所以嫌疑犯是LiveData,Rx,Spek或者他们的组合。

    使用的帮助程序和扩展函数:

    fun instantTaskExecutorRuleStart() =
            ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
                override fun executeOnDiskIO(runnable: Runnable) {
                    runnable.run()
                }
    
                override fun isMainThread(): Boolean {
                    return true
                }
    
                override fun postToMainThread(runnable: Runnable) {
                    runnable.run()
                }
            })
    
    fun instantTaskExecutorRuleFinish() = ArchTaskExecutor.getInstance().setDelegate(null)
    
    fun setRxSchedulersTrampolineOnMain() = RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
    
    fun setTestRxAndLiveData() {
        setRxSchedulersTrampolineOnMain()
        instantTaskExecutorRuleStart()
    }
    
    fun resetTestRxAndLiveData() {
        RxAndroidPlugins.reset()
        instantTaskExecutorRuleFinish()
    }
    
    fun <T> Observable<T>.subscribeIoObserveMain(): Observable<T> =
            subscribeOnIoThread().observeOnMainThread()
    
    fun <T> Observable<T>.subscribeOnIoThread(): Observable<T> = subscribeOn(Schedulers.io())
    
    fun <T> Observable<T>.observeOnMainThread(): Observable<T> =
            observeOn(AndroidSchedulers.mainThread())
    
    2 回复  |  直到 5 年前
        1
  •  0
  •   danypata    5 年前

    我没有在单元测试中使用Speck。我使用了java单元测试平台,它与Rx&完美结合;LiveData,但你必须记住一件事。接收和接收;LiveData是异步的,你不能像 someObserver.subscribe{}, someObserver.doSmth{}, assert{} 这有时会奏效,但这不是正确的方法。

    TestObservers 用于观察接收事件。比如:

    @Test
    public void testMethod() {
       TestObserver<SomeObject> observer = new TestObserver()
       someClass.doSomethingThatReturnsObserver().subscribe(observer)
       observer.assertError(...)
       // or
       observer.awaitTerminalEvent(1, TimeUnit.SECONDS)
       observer.assertValue(somethingReturnedForOnNext)
    }
    

    对于LiveData,还必须使用CountDownLatch等待LiveData执行。像这样:

    @Test
    public void someLiveDataTest() {
       CountDownLatch latch = new CountDownLatch(1); // if you want to check one time exec
       somethingTahtReturnsLiveData.observeForever(params -> {
          /// you can take the params value here
          latch.countDown();
       }
       //trigger live data here
       ....
       latch.await(1, TimeUnit.SECONDS)
       assert(...)
    } 
    

    注1:代码是用JAVA编写的,但是您可以在kotlin中轻松地更改它。

    注2:单例是单元测试的最大敌人;)(静态方法)。

        2
  •  0
  •   Vlad    5 年前

    问题不在于 LiveData ; 这是更常见的问题-单例。在这里 Update...StateUseCases 必须是独生子女;否则,如果观察者得到一个不同的实例,他们将有一个不同的PublishProcessor,而不会得到发布的内容。

    更新…状态用例 通过 ...StateObserver

    状态存在于 更新…状态用例 ,并且由于它是一个单例,所以在两个测试中都会发生更改,并且它们使用相同的实例,从而相互依赖。

    如果不是,则在每个测试组之后重置状态。

    推荐文章