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 withtodayDate
, iftodayDate
is not null.command
’ssetValue
is not called whentodayDate
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.