如何为Android架构组件的生命周期事件添加单元测试?

10

我尝试为支持架构组件生命周期事件的函数添加单元测试。为了支持生命周期事件,我为该函数添加了@OnLifecycleEvent注释,当发生该事件时,我想让该函数执行某些操作。

一切都按预期工作,但我想创建一个单元测试来检查当预期事件发生时我的函数是否运行。

 public class CarServiceProvider implements LifecycleObserver {

    public void bindToLifeCycle(LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void onClear() {
       Log.i("CarServiceProvider", "onClear called");
    }
 }

我尝试模拟LifecycleOwner并创建新的LifecycleRegistery来更改生命周期观察者的状态,但我没有成功。

当状态改变时,如何测试我的onClear()函数是否被调用?

7个回答

14
你应该能够使用 LifecycleRegistry
你的测试将会进行以下操作:
@Test
public void testSomething() {
  LifecycleRegistry lifecycle = new LifecycleRegistry(mock(LifecycleOwner.class));

  // Instantiate your class to test
  CarServiceProvider carServiceProvider = new CarServiceProvider();
  carServiceProvider.bindToLifeCycle(lifecycle);

  // Set lifecycle state
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)

  // Verify that the ON_STOP event was handled, with an assert or similar check
  ...
}

如果您正在测试 Lifecycle.Event.ON_DESTROY,那么在此之前可能需要调用 handleLifecycleEvent(Lifecycle.Event.ON_CREATE)


我不明白这个怎么能行。在你的例子中,lifecycle 的类型是 LifecycleRegistry,但是 bindToLifeCycle 接受的是一个 LifecycleOwner - tir38
好的,那么他会更改该方法的签名,它无论如何都会执行lifecycleOwner.getLifecycle(),因此传递一个lifecycle可能是有意义的。 - Raz
没错,我没有看到LifecycleRegistry实现了Lifecycle。 - tir38

9
只要您正确模拟生命周期所有者,就可以在没有Roboletric的情况下使用单元测试来进行测试。
val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java)
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.markState(Lifecycle.State.RESUMED)
Mockito.`when`(lifecycleOwner.lifecycle).thenReturn(lifecycle)

当您观察变量时,请使用此生命周期所有者,并且您可以使用Mockito.verify查看是否已调用回调。


目前最好的解决方案,您可以调整已弃用的行: lifecycle.currentState = Lifecycle.State.RESUMED - drindt
1
在我的情况下,这会导致崩溃。android.os.Looper中的getMainLooper方法未模拟。 - AouledIssa

3

3

虽然我不是一个大粉丝,但我会使用RobolectricActivityController来实现这个目标。

考虑到应用于Android生命周期工作流的Observer模式的“Observables”是活动、片段等...应用程序上下文是必须的,我们需要某种方式将其引入我们的测试场景中。

我通过这样做达到了预期的结果

build.gradle

testCompile "org.robolectric:robolectric:3.5.1"

CarServiceProviderTest.java

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class CarServiceProviderTest {

    @Test
    public void shouldFireOnClear(){

        //Grab the Activity controller
        ActivityController controller = Robolectric.buildActivity(JustTestActivity.class).create().start();
        AppCompatActivity activity = (AppCompatActivity) controller.get();

        //Instanciate our Observer
        CarServiceProvider carServiceProvider = new CarServiceProvider();
        carServiceProvider.bindToLifeCycle(activity);

        //Fire the expected event
        controller.stop();

        //Assert
        Assert.assertTrue(carServiceProvider.wasFired);
    }
}

CarServiceProvider.java

public class CarServiceProvider implements LifecycleObserver {

    public boolean wasFired;

    public void bindToLifeCycle(LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().addObserver(this);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void onClear() {
        wasFired = true;
    }
}

但是片段怎么办?buildFragment不起作用。 - Morozov

1

在Raz的回答基础上,使用Kotlin可以创建扩展函数以使其更具可重用性。

fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
   val mockLifeCycleOwner: LifecycleOwner = mockk()
   val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
   lifecycleRegistry.addObserver(this)
   lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}

在原问题提问者的情况下,他们有一个fun bindToLifecycle()。如果您也在做类似的事情,您可以为此创建一个扩展函数,以便它适用于所有LifecycleObserver类型:
fun LifecycleObserver.bindToLifeCycle(lifecycle: Lifecycle) {
        lifecycle.addObserver(this);
    }

然后像这样修改testLifeCycleEvent()函数:
fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
   val mockLifeCycleOwner: LifecycleOwner = mockk()
   val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
   this.bindLifecycle(lifecycleRegistry)
   lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}

0
如果您想使用真正的单元测试(而不是AndroidTest)来测试它,最好使用Robolectric,它可以模拟Android框架,而且即将推出Robolectric 4.0。但是,您正在尝试测试与Android Framework的实际交互,因此更适合使用真正的集成测试套件和AndroidTest。您可以进行单元测试,但最可靠的测试方法是在设备上实际调用生命周期事件(Espresso会这样做),并验证其是否被调用。

0

这是一个关于Powermock v3.8和RESUME事件的示例。我使用此事件来展示如何验证livedata值。

#build.gradle

dependencies {
        ...
        testImplementation 'org.robolectric:robolectric:3.8'
        testImplementation "org.powermock:powermock-module-junit4:2.0.2"
        testImplementation "org.powermock:powermock-module-junit4-rule:2.0.2"
        testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
        testImplementation "org.powermock:powermock-classloading-xstream:1.6.4"
        ...
    }

#CustomViewModel.kt

class CustomViewModel() : ViewModel(), LifecycleObserver {
    private val _outputData = MutableLiveData<Boolean>()
    val outputData: LiveData<Boolean> = _outputData

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
       //TODO action
       outputData.value = true
    }
}

#CustomViewModelTest.kt

import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(PowerMockRunner::class)
@PowerMockRunnerDelegate(RobolectricTestRunner::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*")
@Config(manifest = Config.NONE)
class CustomViewModelTest {
    @Mock
    lateinit var observer: Observer<in Boolean>

    @Before
    fun beforeTest() {
        MockitoAnnotations.initMocks(this)
    }

    @Test
    @Config(sdk = [Build.VERSION_CODES.O])
    fun `do an action on resume event`() {
        val vm = spy(CustomViewModel())
        vm.outputData.observeForever(observer)

        vm.testLifecycleEvent(Lifecycle.Event.ON_RESUME)

        verify(vm).onResume()
        verify(observer).onChange(true)
    }
}

fun LifecycleObserver.testLifecycleEvent(lifecycleEvent: Lifecycle.Event) {
    val lifecycleOwner = Mockito.mock(LifecycleOwner::class.java)
    val lifecycleRegistry = LifecycleRegistry(lifecycleOwner)
    this.bindToLifecycle(lifecycleRegistry)
    lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}

fun LifecycleObserver.bindToLifecycle(lifecycle: Lifecycle) {
    lifecycle.addObserver(this)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接