Unit test for repository added

This commit is contained in:
2020-04-20 17:28:18 +01:00
parent 5380cd0c74
commit b9a66c3a79
20 changed files with 143 additions and 55 deletions

View File

@@ -42,8 +42,9 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//mockito //mockito and livedata testing
testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation 'org.mockito:mockito-inline:2.13.0'
implementation 'android.arch.core:core-testing'
//Retrofit and GSON //Retrofit and GSON
implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.squareup.retrofit2:retrofit:2.6.0'

View File

@@ -13,7 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".ui.MainActivity"> <activity android:name=".ui.main.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -1,8 +1,7 @@
package com.example.h_mal.candyspace.data.api package com.example.h_mal.candyspace.data.api
import okhttp3.HttpUrl import com.example.h_mal.candyspace.data.api.model.ApiResponse
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Response import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@@ -15,17 +14,21 @@ interface ApiClass {
@GET("users?") @GET("users?")
suspend fun getUsersFromApi(@Query("inname") inname: String): Response<ApiResponse> suspend fun getUsersFromApi(@Query("inname") inname: String): Response<ApiResponse>
//invoke method creating an invocation of the api call
companion object{ companion object{
operator fun invoke( operator fun invoke(
//injected @params
networkConnectionInterceptor: NetworkConnectionInterceptor, networkConnectionInterceptor: NetworkConnectionInterceptor,
queryParamsInterceptor: QueryParamsInterceptor queryParamsInterceptor: QueryParamsInterceptor
) : ApiClass{ ) : ApiClass{
//okHttpClient with interceptors
val okkHttpclient = OkHttpClient.Builder() val okkHttpclient = OkHttpClient.Builder()
.addNetworkInterceptor(networkConnectionInterceptor) .addNetworkInterceptor(networkConnectionInterceptor)
.addInterceptor(queryParamsInterceptor) .addInterceptor(queryParamsInterceptor)
.build() .build()
//retrofit to be used in @Repository
return Retrofit.Builder() return Retrofit.Builder()
.client(okkHttpclient) .client(okkHttpclient)
.baseUrl("https://api.stackexchange.com/2.2/") .baseUrl("https://api.stackexchange.com/2.2/")

View File

@@ -1,4 +1,6 @@
package com.example.h_mal.candyspace.data.api package com.example.h_mal.candyspace.data.api.model
import com.example.h_mal.candyspace.data.api.model.User
data class ApiResponse( data class ApiResponse(
val items : List<User>?, val items : List<User>?,

View File

@@ -1,4 +1,4 @@
package com.example.h_mal.candyspace.data.api package com.example.h_mal.candyspace.data.api.model
import com.google.gson.annotations.Expose import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@@ -1,4 +1,4 @@
package com.example.h_mal.candyspace.data.api package com.example.h_mal.candyspace.data.api.model
import com.google.gson.annotations.Expose import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@@ -1,15 +1,15 @@
package com.example.h_mal.candyspace.data.repositories package com.example.h_mal.candyspace.data.repositories
import androidx.lifecycle.LiveData
import com.example.h_mal.candyspace.data.api.ApiClass import com.example.h_mal.candyspace.data.api.ApiClass
import com.example.h_mal.candyspace.data.api.ApiResponse import com.example.h_mal.candyspace.data.api.model.ApiResponse
import com.example.h_mal.candyspace.data.api.ResponseUnwrap import com.example.h_mal.candyspace.data.api.ResponseUnwrap
import com.example.h_mal.candyspace.data.api.User
class Repository( class Repository(
private val api: ApiClass private val api: ApiClass
): ResponseUnwrap() { ): ResponseUnwrap() {
//get api response from retrofit class
//then unwrap data object from retrofit response class
suspend fun getUsers(username: String): ApiResponse { suspend fun getUsers(username: String): ApiResponse {
return responseUnwrap { api.getUsersFromApi(username) } return responseUnwrap { api.getUsersFromApi(username) }
} }

View File

@@ -1,26 +1,26 @@
package com.example.h_mal.candyspace.ui.main package com.example.h_mal.candyspace.ui.home
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.example.h_mal.candyspace.R import com.example.h_mal.candyspace.R
import com.example.h_mal.candyspace.data.api.User import com.example.h_mal.candyspace.data.api.model.User
import com.example.h_mal.candyspace.databinding.MainFragmentBinding import com.example.h_mal.candyspace.databinding.MainFragmentBinding
import com.example.h_mal.candyspace.ui.MainActivity.Companion.viewModel import com.example.h_mal.candyspace.ui.main.MainActivity.Companion.viewModel
import com.example.h_mal.candyspace.utils.displayToast import com.example.h_mal.candyspace.ui.user.ListItemViewModel
import com.example.h_mal.candyspace.ui.user.UserProfileFragment
import com.xwray.groupie.GroupAdapter import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.ViewHolder import com.xwray.groupie.ViewHolder
/* /**
* UI for the first screen holding the list, search box * UI for the first screen holding the list, search box
*/ */
class MainFragment : Fragment(){ class MainFragment : Fragment(){
companion object { companion object {
@@ -41,26 +41,37 @@ class MainFragment : Fragment(){
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
//observer the live data of what is retrieved from the API call
viewModel.usersLiveData.observe(viewLifecycleOwner, Observer { viewModel.usersLiveData.observe(viewLifecycleOwner, Observer {
//create adapter for viewbinding into the recycler view
val mAdapter = GroupAdapter<ViewHolder>().apply { val mAdapter = GroupAdapter<ViewHolder>().apply {
addAll(it.toUserViewModels()) addAll(it.toUserViewModels())
} }
//setup the recyclerview
binding.recyclerView.apply { binding.recyclerView.apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
setHasFixedSize(true) setHasFixedSize(true)
adapter = mAdapter adapter = mAdapter
} }
/*
* Item click listener for recyclerView
*
*/
mAdapter.setOnItemClickListener { item, _ -> mAdapter.setOnItemClickListener { item, _ ->
//get the position of the item clicked
val i = mAdapter.getAdapterPosition(item) val i = mAdapter.getAdapterPosition(item)
//set user in @MainViewModel
viewModel.setCurrentUser(it[i]) viewModel.setCurrentUser(it[i])
/**
* display [UserProfileFragment]
*/
activity!!.supportFragmentManager.beginTransaction() activity!!.supportFragmentManager.beginTransaction()
.replace(R.id.container, UserProfileFragment()) .replace(R.id.container,
UserProfileFragment()
)
.addToBackStack("user") .addToBackStack("user")
.commit() .commit()
} }

View File

@@ -1,5 +1,8 @@
package com.example.h_mal.candyspace.ui.main package com.example.h_mal.candyspace.ui.main
/**
* completion listener for [MainViewModel] when handling async calls
*/
interface CompletionListener { interface CompletionListener {
fun onStarted() fun onStarted()
fun onSuccess() fun onSuccess()

View File

@@ -1,24 +1,22 @@
package com.example.h_mal.candyspace.ui package com.example.h_mal.candyspace.ui.main
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.example.h_mal.candyspace.R import com.example.h_mal.candyspace.R
import com.example.h_mal.candyspace.ui.main.CompletionListener import com.example.h_mal.candyspace.ui.home.MainFragment
import com.example.h_mal.candyspace.ui.main.MainFragment
import com.example.h_mal.candyspace.ui.main.MainViewModel
import com.example.h_mal.candyspace.ui.main.MainViewModelFactory
import com.example.h_mal.candyspace.utils.displayToast import com.example.h_mal.candyspace.utils.displayToast
import com.example.h_mal.candyspace.utils.hide import com.example.h_mal.candyspace.utils.hide
import com.example.h_mal.candyspace.utils.show import com.example.h_mal.candyspace.utils.show
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein import org.kodein.di.android.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
/**
* [MainActivity] hosting the fragments and controlling a lot of the UI
*/
class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener { class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
//retrieve the viewmodel factory from the kodein dependency injection //retrieve the viewmodel factory from the kodein dependency injection
@@ -35,6 +33,7 @@ class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity) setContentView(R.layout.main_activity)
//setup home button for back navigation
supportActionBar?.setDisplayHomeAsUpEnabled(true); supportActionBar?.setDisplayHomeAsUpEnabled(true);
supportActionBar?.setDisplayShowHomeEnabled(true); supportActionBar?.setDisplayShowHomeEnabled(true);
@@ -43,11 +42,15 @@ class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
viewModel.completionListener = this viewModel.completionListener = this
if (savedInstanceState == null) { if (savedInstanceState == null) {
//display first fragment
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment()) .replace(R.id.container,
MainFragment()
)
.commit() .commit()
} }
//fragment change listener to display title based on current fragment
supportFragmentManager.addOnBackStackChangedListener { supportFragmentManager.addOnBackStackChangedListener {
val name = when (supportFragmentManager.fragments[0]::class.java.simpleName) { val name = when (supportFragmentManager.fragments[0]::class.java.simpleName) {
"UserProfileFragment" -> "User" "UserProfileFragment" -> "User"
@@ -57,6 +60,11 @@ class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
} }
} }
/*
* Back button over override
* - close app if home fragment
* - return to previous fragment if User fragment
*/
override fun onBackPressed() { override fun onBackPressed() {
if(supportFragmentManager.backStackEntryCount > 0){ if(supportFragmentManager.backStackEntryCount > 0){
supportFragmentManager.popBackStack() supportFragmentManager.popBackStack()
@@ -66,6 +74,7 @@ class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
} }
//When home button in toolbar is pressed
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> onBackPressed() android.R.id.home -> onBackPressed()
@@ -73,15 +82,21 @@ class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
/*
* completion listener methods
*/
override fun onStarted() { override fun onStarted() {
//show loading
progress_circular.show() progress_circular.show()
} }
override fun onSuccess() { override fun onSuccess() {
//loading completed - hide loading
progress_circular.hide() progress_circular.hide()
} }
override fun onFailure(message: String) { override fun onFailure(message: String) {
//loading failed - hide loading and display toast of error
progress_circular.hide() progress_circular.hide()
displayToast(message) displayToast(message)
} }

View File

@@ -3,15 +3,10 @@ package com.example.h_mal.candyspace.ui.main
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import androidx.core.content.ContextCompat.getSystemService
import androidx.databinding.BindingAdapter
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.example.h_mal.candyspace.R import com.example.h_mal.candyspace.data.api.model.User
import com.example.h_mal.candyspace.data.api.User
import com.example.h_mal.candyspace.data.repositories.Repository import com.example.h_mal.candyspace.data.repositories.Repository
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -21,29 +16,39 @@ class MainViewModel(
private val repository: Repository private val repository: Repository
) : ViewModel() { ) : ViewModel() {
// string binded to the edittext input in @R.layout.main_fragment
var searchString: String? = null var searchString: String? = null
var completionListener: CompletionListener? = null var completionListener: CompletionListener? = null
//data objects - live data and var
val usersLiveData = MutableLiveData<List<User>>() val usersLiveData = MutableLiveData<List<User>>()
var currentUserLiveData: User? = null var currentUserLiveData: User? = null
fun submit(view: View){ /**
* view binding of the submit button in @R.layout.main_fragment
*/
fun submit(view: View?){
//close keyboard when clicked
view.let { v -> view.let { v ->
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager val imm = view?.context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.hideSoftInputFromWindow(v.windowToken, 0) imm?.hideSoftInputFromWindow(v?.windowToken, 0)
} }
completionListener?.onStarted() completionListener?.onStarted()
//return if search string is empty
if (searchString.isNullOrEmpty()){ if (searchString.isNullOrEmpty()){
completionListener?.onFailure("Search box is empty") completionListener?.onFailure("Search box is empty")
return return
} }
//open a coroutine on the Main thread and update views upon load
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
try { try {
//retrieve response from API call
val apiResponse = repository.getUsers(searchString!!) val apiResponse = repository.getUsers(searchString!!)
//unwrap list of user out of ApiResponse
apiResponse.items?.let { apiResponse.items?.let {
if (it.isNotEmpty()){ if (it.isNotEmpty()){
//update live data //update live data

View File

@@ -4,6 +4,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.example.h_mal.candyspace.data.repositories.Repository import com.example.h_mal.candyspace.data.repositories.Repository
/**
* Viewmodel factory for [MainViewModel]
* @repository injected into MainViewModel
*/
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MainViewModelFactory( class MainViewModelFactory(
private val repository: Repository private val repository: Repository

View File

@@ -1,7 +1,7 @@
package com.example.h_mal.candyspace.ui.main package com.example.h_mal.candyspace.ui.user
import com.example.h_mal.candyspace.R import com.example.h_mal.candyspace.R
import com.example.h_mal.candyspace.data.api.User import com.example.h_mal.candyspace.data.api.model.User
import com.example.h_mal.candyspace.databinding.ListItemLayoutBinding import com.example.h_mal.candyspace.databinding.ListItemLayoutBinding
import com.xwray.groupie.databinding.BindableItem import com.xwray.groupie.databinding.BindableItem

View File

@@ -1,15 +1,13 @@
package com.example.h_mal.candyspace.ui.main package com.example.h_mal.candyspace.ui.user
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import com.example.h_mal.candyspace.R import com.example.h_mal.candyspace.R
import com.example.h_mal.candyspace.databinding.FragmentUserProfileBinding import com.example.h_mal.candyspace.databinding.FragmentUserProfileBinding
import com.example.h_mal.candyspace.ui.MainActivity import com.example.h_mal.candyspace.ui.main.MainActivity.Companion.viewModel
import com.example.h_mal.candyspace.ui.MainActivity.Companion.viewModel
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
/** /**
@@ -20,7 +18,8 @@ import com.squareup.picasso.Picasso
class UserProfileFragment : Fragment() { class UserProfileFragment : Fragment() {
companion object { companion object {
fun newInstance() = UserProfileFragment() fun newInstance() =
UserProfileFragment()
} }
lateinit var binding: FragmentUserProfileBinding lateinit var binding: FragmentUserProfileBinding
@@ -29,13 +28,13 @@ class UserProfileFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
activity?.actionBar?.setHomeButtonEnabled(true) // Inflate the layout for this fragment into data binding
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_profile, container, false) binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_profile, container, false)
return binding.root return binding.root
} }
//Update the data for viewbinding onResume as data would have changed when selecting a new user
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.currentUserLiveData?.let { viewModel.currentUserLiveData?.let {

View File

@@ -2,14 +2,14 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.main.UserProfileFragment"> tools:context=".ui.user.UserProfileFragment">
<data> <data>
<import type="com.example.h_mal.candyspace.R"/> <import type="com.example.h_mal.candyspace.R"/>
<import type="com.example.h_mal.candyspace.utils.ConverterUtil"/> <import type="com.example.h_mal.candyspace.utils.ConverterUtil"/>
<variable <variable
name="user" name="user"
type="com.example.h_mal.candyspace.data.api.User" /> type="com.example.h_mal.candyspace.data.api.model.User" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View File

@@ -6,7 +6,7 @@
<data> <data>
<variable <variable
name="user" name="user"
type="com.example.h_mal.candyspace.data.api.User" /> type="com.example.h_mal.candyspace.data.api.model.User" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View File

@@ -4,7 +4,7 @@
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.MainActivity" > tools:context=".ui.main.MainActivity" >
<ProgressBar <ProgressBar
android:id="@+id/progress_circular" android:id="@+id/progress_circular"

View File

@@ -13,7 +13,7 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.main.MainFragment"> tools:context=".ui.home.MainFragment">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -1,8 +1,7 @@
package com.example.h_mal.candyspace.data.repositories package com.example.h_mal.candyspace.data.repositories
import com.example.h_mal.candyspace.data.api.ApiClass import com.example.h_mal.candyspace.data.api.ApiClass
import com.example.h_mal.candyspace.data.api.ApiResponse import com.example.h_mal.candyspace.data.api.model.ApiResponse
import com.example.h_mal.candyspace.data.api.User
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody

View File

@@ -1,12 +1,58 @@
package com.example.h_mal.candyspace.ui.main package com.example.h_mal.candyspace.ui.main
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.example.h_mal.candyspace.data.api.ApiClass
import com.example.h_mal.candyspace.data.api.model.ApiResponse
import com.example.h_mal.candyspace.data.api.model.User
import com.example.h_mal.candyspace.data.repositories.Repository
import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import retrofit2.Response
import java.io.IOException
class MainViewModelTest { class MainViewModelTest {
@get:Rule
var rule: TestRule = InstantTaskExecutorRule()
lateinit var viewModel: MainViewModel
@Mock
lateinit var repository: Repository
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this)
viewModel = MainViewModel(repository)
}
@Test
fun getApiFromRepository_SuccessfulReturn() = runBlocking{
val user = mock(User::class.java)
val mockApiResponse = ApiResponse(listOf(user),null,null,null)
Mockito.`when`(repository.getUsers("12345")).thenReturn(mockApiResponse)
viewModel.submit(null)
assertEquals(mockApiResponse.items, viewModel.usersLiveData.value)
}
@Test
fun getApiFromRepository_unsuccessfulReturn() = runBlocking{
Mockito.`when`(repository.getUsers("12345")).thenAnswer{ throw IOException() }
viewModel.submit(null)
assertEquals(null, viewModel.usersLiveData.value)
} }
} }