测试 Android Compose State 值

Rob*_*kus 9 junit android kotlin android-jetpack-compose

我已经实现了公开 ComposeState<T>实例的 Android 视图模型。例如,查看模型实现:

class MyViewModel<S> {
    private val _viewState: MutableState<S> = mutableStateOf(initialState)
    val viewState: State<S> = _viewState
    ...
    protected fun setState(newState: S) {
        _viewState.value = newState
    }
}
Run Code Online (Sandbox Code Playgroud)

我想在单元测试中测试它将设置什么值/状态。只是我想要实现的目标的一个简短示例:

class MyViewModelTest {
    @Test
    fun `when view model initialized then should emit initial state first`() {
        val viewModel = MyViewModel()
        assertEquals(InitialState(), viewModel.viewState.value)
    }

    @Test
    fun `when view model interacted then should emit result state`() {
        val viewModel = MyViewModel()
        val expectedState = NewState()

        viewModel.setState(expectedState)

        assertEquals(expectedState, viewModel.viewState.value)
    }
}
Run Code Online (Sandbox Code Playgroud)

可以测试吗State<T>?如果将状态值存储在视图模型端,你们如何进行单元测试?

Sha*_*bot 4

对于我的特定用例,我使用协程和单向数据流,但通过mutableStateOf. 正在寻找单元测试的初始状态+事件的影响,就像你的情况一样。

查看型号:

class MyViewModel(
  // For injecting test dispatcher
  private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ViewModel() {

  // State exposed via delegate
  var state by mutableStateOf(State.Show())
    private set

  fun onEvent(event: Event) {
    when (event) {
      is Event.Greet -> greet(event)
      else -> TODO("Other Stuff")
    }
  }

  private fun greet(greet: Event.Greet) {
    viewModelScope.launch(dispatcher) {
      // Do some, ya know, API stuff!
      state = State.Show("Hello ${greet.name}")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

state通过委托和私有设置器公开。初始值为Show("Hello")。我们还可以直接使用导入设置更新:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
Run Code Online (Sandbox Code Playgroud)

调度程序的注入对于控制进程至关重要。

状态和事件,为了完整性

// Encapsulates possible states
sealed class State {

  // Data class calculates equality from the constructor
  // aiding with assertEquals
  data class Show(
    val greeting: String = "Hello"
  ) : State()
}

// ... and possible Events
sealed class Event {
  data class Greet(val name: String) : Event()
}
Run Code Online (Sandbox Code Playgroud)

通过此设置,我们可以进行单元测试:

@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class MyViewModelTest {

  val dispatcher = StandardTestDispatcher()

  @Test
  fun `viewModel shows initial state`() = runTest {
    val vm = MyViewModel(dispatcher)
    val expectedState = State.Show()

    // Get the initial state
    val state = vm.state

    assertEquals(expectedState, state)
  }

  @Test
  fun `viewModel shows updated state`() = runTest {
    val vm = MyViewModel(dispatcher)
    val expectedState = State.Show("Hello Robertus")

    // Trigger an event
    vm.onEvent(Event.Greet("Robertus"))

    // Await the change
    dispatcher.scheduler.advanceUntilIdle()

    // Get the state
    val state = vm.state

    assertEquals(expectedState, state)
  }
}
Run Code Online (Sandbox Code Playgroud)

val dispatcher = StandardTestDispatcher()我们传入一个我们可以控制的调度程序来等待协程。我们在测试中以及需要等待它们运行和更新状态时将其传递给 ViewModel。

注意:测试在表达式内 = runTest { }

对于第一个测试,viewModel shows initial state,我们得到初始化时使用的初始值mutableStatOf

在第二个测试中,我们通过在传递和获取之间viewModel shows updated state调用来等待协程完成(调度程序空闲)。dispatcher.scheduler.advanceUntilIdle()Eventstate

没有它,我们将得到初始状态。 在此输入图像描述