A SingleLiveEvent class is an observable live data that can be used to send events from ViewModel to View in Android MVVM-styled designs.

This link describes using Kotlin’s sealed data class with a SingleLiveEvent to create such a mechanism.

I used that in one of my projects and ended up with a ViewModel that looks like this:

ViewModel Class

val command: SingleLiveEvent<Command> = dependencyProvider.getCommmand()

// This value will be set by other business logic
var todayDate : String? = null

fun handleButtonClick(){
   todayDate?.let {
      command.value = Command.LaunchNextActivity(it)
   }
}

Command Sealed Class

sealed class Command {
    class LaunchNextActivity(val dateString : String) : Command()
}

How does it work?

A user event will trigger this handleButtonClick() in my ViewModel class.

It must then instruct a View to launch NextActivity using a variable todayDate.

The todayDate value is set by some other UseCase class.

I am using a simple Kotlin object class to provide dependencies, SingleLiveEvent<Command> type class being one of them.

interface DependencyProvider {
    fun getCommmand(): SingleLiveEvent<Command>
}

object Injector : DependencyProvider {
    override fun getCommmand(): SingleLiveEvent<Command> {
        return SingleLiveEvent()
    }
}

Writing a Unit Test

When I am writing unit test, I often find myself stuck with the question, “What to check?” I usually got out by thinking inwardly and selfishly. What is the single concern for this function?

So in this case, I am not concerned with who is observing this command event. But I am concerned about:

  • command’s value being set with todayDate, if todayDate is not null.
  • command’s setValue is not called when todayDate is null.

Let us address the first concern, which is to be able to verify that command’s setValue is being called.

@Mock
lateinit var mockProvider: DependencyProvider

@Mock
lateinit var mockCommand : SingleLiveEvent<Command>

@Before
fun setUp() {
    MockitoAnnotations.initMocks(this)
    whenever(mockProvider.getCommmand()).thenReturn(mockCommand)
    viewModel = MyViewModel(mockProvider)
}

@Test
fun handleButtonClick_whenTodayDateNotNull() {
    val testDate = "2018-07-22"

    //Pretend here that some UseCase sets todayDate as testDate
    viewModel.todayDate = testDate
    viewModel.handleButtonClick()

    val argumentCaptor : ArgumentCaptor<Command.LaunchNextActivity> = 
            ArgumentCaptor.forClass(Command.LaunchNextActivity::class.java)
  
    verify(mockCommand, times(1)).setValue(argumentCaptor.capture())
    Assert.assertEquals(testDate,  argumentCaptor.value.dateString)
}

Mocking Dependencies

For us to verify command’s function is being called, we need to mock it.

@Mock
lateinit var mockCommand : SingleLiveEvent<Command>

We want to override the provideCommand() to get it to return mockCommand, so we need to mock our Injector as well. This mockProvider is passed into our ViewModel, see line 5.

@Mock
lateinit var mockProvider: DependencyProvider
...
whenever(mockProvider.getCommmand()).thenReturn(mockCommand)

All these mocks will be initialised when the following function is called.

MockitoAnnotations.initMocks(this)

There, we have bent the dependencies to our will, we can move on now.

Verifying and Asserting

Line 19. We can verify whether or not setValue is being called by specifying any(). This means we do not really care about the value of the parameter that is passed into the setValue function.

verify(mockCommand, times(1)).setValue(any())

So to check that it is not called,

verify(mockCommand, times(0)).setValue(any())

However, we ARE concerned that the correct value is being used for setValue(). Mockito’s ArgumentCaptor class do that.

They allow you to capture the variable that is being passed as an argument, for the function that you are verifying.

verify(mockCommand, times(1)).setValue(argumentCaptor.capture())
Assert.assertEquals(testDate, argumentCaptor.value.dateString)

After capturing, we can then perform simple assertions on the captured value.

Summary

Kotlin’s sealed class can be used effectively to send events with parameters to View classes, from ViewModels.

These sealed classes are wrapped with SingleLiveEvent live data classes, so Views can create observers to observe changes in value.

We can use Mockito’s ArgumentCaptors to capture what is being used to set these live data, thereby verifying the correctness of the function.

Original post by Boon Keat at here.