How can I add unit test for android architecture components life cycle event?
P

7

10

I tried to add a unit test for my function which supports architecture components lifecycle event. To support lifecycle event, I added the @OnLifecycleEvent annotation for my function which I want to do something when that event occurred.

Everything is working as expected but I want to create a unit test for that function to check my function running when the intended event occurred.

 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");
    }
 }

I tried to mock LifecycleOwner and create new LifecycleRegistery to change the state of lifecycle observer but I didn't do.

How can I test my onClear() function called when state changed ?

Pelagian answered 29/6, 2018 at 9:52 Comment(0)
R
3

Despite of I am not a big fan, I would go with Robolectric making use of ActivityController to achieve this.

Given the fact that the "Observables" of the Observer pattern applied in Android Lifecycle workflow are Activities, Fragments... An application context is a must and we need somehow bring it to our test scenario.

I achieved the expected result by doing this

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;
    }
}
Rici answered 12/7, 2018 at 23:24 Comment(1)
but how about fragment? buildFragment doesn' t workWilbertwilborn
P
14

You should be able to use the LifecycleRegistry

Your test would do something like below:

@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
  ...
}

If you are testing Lifecycle.Event.ON_DESTROY then you probably need to call handleLifecycleEvent(Lifecycle.Event.ON_CREATE) prior to this.

Privity answered 15/10, 2018 at 6:11 Comment(3)
I don't see how this can work. In your example lifecycle is of type LifecycleRegistry, but bindToLifeCycle takes in a LifecycleOwner.Tanked
Well, he would then change the signature of that method, it does lifecycleOwner.getLifecycle() anyway so passing a lifecycle would probably make sense anywayPrivity
:thumbsup: Right. I didn't see that LifecycleRegistry implements LifecycleTanked
T
9

You can test using Unit tests without Roboletric as long as you mock the lifecycle owner properly.

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)

Use this lifecycleOwner when you observe a variable and you can use Mockito.verify to see if your callback has been called

Turnbuckle answered 6/1, 2020 at 14:23 Comment(2)
Best solution so far, you could adjust the line which is deprecated: lifecycle.currentState = Lifecycle.State.RESUMEDCantle
In my case this results in a crash java Method getMainLooper in android.os.Looper not mocked. Significancy
S
4

You can use the TestLifecycleOwner from the

androidx.lifecycle:lifecycle-runtime-testing

dependency

https://developer.android.com/reference/kotlin/androidx/lifecycle/testing/TestLifecycleOwner

Siriasis answered 16/9, 2022 at 9:21 Comment(0)
R
3

Despite of I am not a big fan, I would go with Robolectric making use of ActivityController to achieve this.

Given the fact that the "Observables" of the Observer pattern applied in Android Lifecycle workflow are Activities, Fragments... An application context is a must and we need somehow bring it to our test scenario.

I achieved the expected result by doing this

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;
    }
}
Rici answered 12/7, 2018 at 23:24 Comment(1)
but how about fragment? buildFragment doesn' t workWilbertwilborn
W
1

Expanding on Raz's answer, in Kotlin you can make an extension function to make this more reusable.

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

In the original questioner's case, they have a fun bindToLifecycle(). If you are doing something like that, you can just make an extension function for that as well so it applies to all LifecycleObserver types:

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

and then modify testLifeCycleEvent() like this:

fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
   val mockLifeCycleOwner: LifecycleOwner = mockk()
   val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
   this.bindLifecycle(lifecycleRegistry)
   lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}
Waxman answered 15/3, 2021 at 15:54 Comment(0)
F
0

If you want to test it with a real Unit test (not AndroidTest), your best bet is to use Robolectric, it mocks the Android framework and Robolectric 4.0 is coming out. However you're trying to test the actual interaction with the Android Framework, so that's a task better suited for a true integration testing suite and an AndroidTest. You can unit test it but the most sure way to test it would be to actually invoke the lifecycle event on a device (What Espresso would do) and verify it's called.

Flatto answered 5/7, 2018 at 22:21 Comment(0)
S
0

There is a sample for Powermock v3.8 and RESUME event. I use this event to show how to verify livedata value

#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)
}
Salamone answered 28/3, 2021 at 20:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.