------------------- | Counter: 0 | | [Increment] | -------------------
ViewModel testing in Android Kotlin - Mini App: Build & Ship
import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class CounterViewModel : ViewModel() { private val _counter = MutableLiveData<Int>(0) val counter: LiveData<Int> = _counter // TODO: Add increment function here }
import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class CounterViewModel : ViewModel() { private val _counter = MutableLiveData<Int>(0) val counter: LiveData<Int> = _counter fun increment() { val current = _counter.value ?: 0 _counter.value = current + 1 } } // Test file import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mockito.mock import org.mockito.Mockito.verify class CounterViewModelTest { @get:Rule val instantExecutorRule = InstantTaskExecutorRule() private lateinit var viewModel: CounterViewModel private lateinit var observer: Observer<Int> @Before fun setup() { viewModel = CounterViewModel() observer = mock(Observer::class.java) as Observer<Int> viewModel.counter.observeForever(observer) } @Test fun initialCounterIsZero() { assertEquals(0, viewModel.counter.value) } @Test fun incrementIncreasesCounter() { viewModel.increment() assertEquals(1, viewModel.counter.value) viewModel.increment() assertEquals(2, viewModel.counter.value) } }
This ViewModel holds a counter as LiveData starting at 0. The increment() function increases the counter by 1 safely handling null values. The unit test uses InstantTaskExecutorRule to run LiveData synchronously. It observes the LiveData with a mocked observer to avoid leaks. The tests check the initial value is zero and that calling increment() updates the counter correctly.
------------------- | Counter: 0 | | [Increment] | -------------------
Practice
What is the main purpose of testing a ViewModel in Android development?
Solution
Step 1: Understand ViewModel role
The ViewModel holds and manages UI-related data but does not handle UI rendering.Step 2: Identify testing focus
Testing ViewModel focuses on data logic and state management, not UI or navigation.Final Answer:
To verify the data handling and business logic without UI involvement -> Option BQuick Check:
ViewModel testing = data logic check [OK]
- Confusing ViewModel testing with UI testing
- Thinking ViewModel tests check screen layouts
- Assuming navigation is tested in ViewModel tests
Which annotation is commonly used to set up a ViewModel instance before each test method runs?
Solution
Step 1: Recall JUnit lifecycle annotations
@Before runs before each test method to prepare test environment.Step 2: Match annotation to ViewModel setup
Use @Before to initialize ViewModel before tests run.Final Answer:
@Before -> Option DQuick Check:
Setup before tests = @Before [OK]
- Using @Test instead of @Before for setup
- Confusing @After with setup annotation
- Skipping setup annotation entirely
Given the following test code snippet, what will be the value of result after running the test?
class MyViewModelTest {
private lateinit var viewModel: MyViewModel
@Before
fun setup() {
viewModel = MyViewModel()
}
@Test
fun testIncrement() {
viewModel.increment()
val result = viewModel.counter
}
}Assuming counter starts at 0 and increment() adds 1.
Solution
Step 1: Understand initial state
ViewModel's counter starts at 0 before increment.Step 2: Apply increment method
Calling increment() adds 1, so counter becomes 1.Final Answer:
1 -> Option AQuick Check:
Initial 0 + 1 increment = 1 [OK]
- Assuming counter remains 0 after increment
- Thinking result is null without initialization
- Expecting an exception without error in code
Identify the error in this ViewModel test code snippet:
class SampleViewModelTest {
private lateinit var viewModel: SampleViewModel
@Test
fun testReset() {
viewModel.reset()
assertEquals(0, viewModel.counter)
}
}Assuming viewModel is not initialized anywhere else.
Solution
Step 1: Check viewModel initialization
viewModel is declared but never initialized before test runs.Step 2: Identify missing setup
Without @Before method to initialize viewModel, calling reset() causes error.Final Answer:
Missing @Before setup to initialize viewModel before test -> Option AQuick Check:
Initialize viewModel before test = @Before needed [OK]
- Forgetting to initialize viewModel before use
- Mixing assertEquals parameter order
- Assuming reset() method is missing without checking
You want to test a ViewModel method that fetches data asynchronously and updates a LiveData property. Which approach is best to test this behavior?
Solution
Step 1: Understand asynchronous data fetching
ViewModel uses coroutines to fetch data and update LiveData asynchronously.Step 2: Use coroutine test tools
Coroutine test rules allow running suspend functions in tests synchronously for reliable assertions.Step 3: Observe LiveData changes properly
Observing LiveData synchronously ensures test waits for updates before asserting values.Final Answer:
Use a coroutine test rule to run suspend functions and observe LiveData changes synchronously -> Option CQuick Check:
Async ViewModel test = coroutine test rule + LiveData observe [OK]
- Checking LiveData immediately without waiting
- Mocking LiveData instead of testing real updates
- Testing UI instead of ViewModel logic
Ready to prove it?
Pass the test and this pattern moves unknown
