使用存储库模式和 LiveData 对 ViewModel 进行单元测试
Unit test ViewModel using repository pattern and LiveData
我想为我的视图模型编写单元测试 class :
class MainViewModel(
repository: ShowRepository
) : ViewModel() {
private val _shows = repository.shows
val shows: LiveData<MyResult<List<Show>>>
get() = _shows
}
这是我的存储库 class :
class ShowRepository(
private val dao: ShowDao,
private val api: TVMazeService,
private val context: Context
) {
/**
* A list of shows that can be shown on the screen.
*/
val shows = resultLiveData(
databaseQuery = {
Transformations.map(dao.getShows()) {
it.asDomainModel()
}
},
networkCall = { refreshShows() })
/**
* Refresh the shows stored in the offline cache.
*/
suspend fun refreshShows(): MyResult<List<Show>> =
try {
if (isNetworkAvailable(context)) {
val shows = api.fetchShowList().await()
dao.insertAll(*shows.asDatabaseModel())
MyResult.success(shows)
} else {
MyResult.error(context.getString(R.string.failed_internet_msg))
}
} catch (err: HttpException) {
MyResult.error(context.getString(R.string.failed_loading_msg))
} catch (err: UnknownHostException) {
MyResult.error(context.getString(R.string.failed_unknown_host_msg))
} catch (err: SocketTimeoutException) {
MyResult.error(context.getString(R.string.failed_socket_timeout_msg))
}
}
这是我的道 class :
@Dao
interface ShowDao {
/**
* Select all shows from the shows table.
*
* @return all shows.
*/
@Query("SELECT * FROM databaseshow")
fun getShows(): LiveData<List<DatabaseShow>>
}
这是我的单元测试:
@ExperimentalCoroutinesApi
class MainViewModelTest {
private lateinit var viewModel: MainViewModel
private lateinit var repository: ShowRepository
private val api: TVMazeService = mock()
private val dao: ShowDao = mock()
private val context: Context = mock()
@Test
fun fetch() {
val observer1: Observer<List<DatabaseShow>> = mock()
dao.getShows().observeForever(observer1)
repository = ShowRepository(dao, api, context)
val observer2: Observer<MyResult<List<Show>>> = mock()
repository.shows.observeForever(observer2)
viewModel = MainViewModel(repository)
val observer3: Observer<MyResult<List<Show>>> = mock()
viewModel.shows.observeForever(observer3)
verify(viewModel).shows
}
}
但我收到以下异常:
java.lang.NullPointerException
at com.android.sample.tvmaze.viewmodel.MainViewModelTest.fetch(MainViewModelTest.kt:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=14=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
如果你能给我任何指导,我将不胜感激。
我会模拟存储库并指示 mockito true Mockito.when().doReturn()
到 return 一些数据,并验证 LiveData
输出是否正确。
当然你可以使用一个实例ShowRepository
。当执行命中模拟对象时,您仍然需要指示 mockito 如何 return。和以前一样,您可以更改行为 w
这一行是错误的verify(viewModel).shows
。验证只能在模拟上调用。 viewModel
是一个实例,因此,一旦执行到该行,您的测试就会失败。
对于 LiveData 的单元测试,您可能需要以下规则
@get:Rule
var rule: TestRule = InstantTaskExecutorRule()
我将 Dao 方法更改为 return Flow 而不是 LiveData :
@Dao
interface ShowDao {
/**
* Select all shows from the shows table.
*
* @return all shows.
*/
@Query("SELECT * FROM databaseshow")
fun getShows(): Flow<List<DatabaseShow>>
}
而且我可以成功 运行 我的测试,例如:
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
mockkStatic("com.android.sample.tvmaze.util.ContextExtKt")
every {
context.isNetworkAvailable()
} returns true
`when`(api.fetchShowList()).thenReturn(Calls.response(Response.success(emptyList())))
`when`(dao.getShows()).thenReturn(flowOf(emptyList()))
val repository = ShowRepository(dao, api, context, TestContextProvider())
val viewModel = MainViewModel(repository).apply {
shows.observeForever(resource)
}
try {
verify(resource).onChanged(Resource.loading())
verify(resource).onChanged(Resource.success(emptyList()))
} finally {
viewModel.shows.removeObserver(resource)
}
}
我想为我的视图模型编写单元测试 class :
class MainViewModel(
repository: ShowRepository
) : ViewModel() {
private val _shows = repository.shows
val shows: LiveData<MyResult<List<Show>>>
get() = _shows
}
这是我的存储库 class :
class ShowRepository(
private val dao: ShowDao,
private val api: TVMazeService,
private val context: Context
) {
/**
* A list of shows that can be shown on the screen.
*/
val shows = resultLiveData(
databaseQuery = {
Transformations.map(dao.getShows()) {
it.asDomainModel()
}
},
networkCall = { refreshShows() })
/**
* Refresh the shows stored in the offline cache.
*/
suspend fun refreshShows(): MyResult<List<Show>> =
try {
if (isNetworkAvailable(context)) {
val shows = api.fetchShowList().await()
dao.insertAll(*shows.asDatabaseModel())
MyResult.success(shows)
} else {
MyResult.error(context.getString(R.string.failed_internet_msg))
}
} catch (err: HttpException) {
MyResult.error(context.getString(R.string.failed_loading_msg))
} catch (err: UnknownHostException) {
MyResult.error(context.getString(R.string.failed_unknown_host_msg))
} catch (err: SocketTimeoutException) {
MyResult.error(context.getString(R.string.failed_socket_timeout_msg))
}
}
这是我的道 class :
@Dao
interface ShowDao {
/**
* Select all shows from the shows table.
*
* @return all shows.
*/
@Query("SELECT * FROM databaseshow")
fun getShows(): LiveData<List<DatabaseShow>>
}
这是我的单元测试:
@ExperimentalCoroutinesApi
class MainViewModelTest {
private lateinit var viewModel: MainViewModel
private lateinit var repository: ShowRepository
private val api: TVMazeService = mock()
private val dao: ShowDao = mock()
private val context: Context = mock()
@Test
fun fetch() {
val observer1: Observer<List<DatabaseShow>> = mock()
dao.getShows().observeForever(observer1)
repository = ShowRepository(dao, api, context)
val observer2: Observer<MyResult<List<Show>>> = mock()
repository.shows.observeForever(observer2)
viewModel = MainViewModel(repository)
val observer3: Observer<MyResult<List<Show>>> = mock()
viewModel.shows.observeForever(observer3)
verify(viewModel).shows
}
}
但我收到以下异常:
java.lang.NullPointerException
at com.android.sample.tvmaze.viewmodel.MainViewModelTest.fetch(MainViewModelTest.kt:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=14=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
如果你能给我任何指导,我将不胜感激。
我会模拟存储库并指示 mockito true Mockito.when().doReturn()
到 return 一些数据,并验证 LiveData
输出是否正确。
当然你可以使用一个实例ShowRepository
。当执行命中模拟对象时,您仍然需要指示 mockito 如何 return。和以前一样,您可以更改行为 w
这一行是错误的verify(viewModel).shows
。验证只能在模拟上调用。 viewModel
是一个实例,因此,一旦执行到该行,您的测试就会失败。
对于 LiveData 的单元测试,您可能需要以下规则
@get:Rule
var rule: TestRule = InstantTaskExecutorRule()
我将 Dao 方法更改为 return Flow 而不是 LiveData :
@Dao
interface ShowDao {
/**
* Select all shows from the shows table.
*
* @return all shows.
*/
@Query("SELECT * FROM databaseshow")
fun getShows(): Flow<List<DatabaseShow>>
}
而且我可以成功 运行 我的测试,例如:
@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
mockkStatic("com.android.sample.tvmaze.util.ContextExtKt")
every {
context.isNetworkAvailable()
} returns true
`when`(api.fetchShowList()).thenReturn(Calls.response(Response.success(emptyList())))
`when`(dao.getShows()).thenReturn(flowOf(emptyList()))
val repository = ShowRepository(dao, api, context, TestContextProvider())
val viewModel = MainViewModel(repository).apply {
shows.observeForever(resource)
}
try {
verify(resource).onChanged(Resource.loading())
verify(resource).onChanged(Resource.success(emptyList()))
} finally {
viewModel.shows.removeObserver(resource)
}
}