View binding added, basic repository unit tests added

This commit is contained in:
2020-04-19 21:38:36 +01:00
parent 2a711248af
commit a5dffc276c
7 changed files with 173 additions and 50 deletions

View File

@@ -24,6 +24,10 @@ android {
}
}
dataBinding {
enabled = true
}
}
dependencies {
@@ -37,6 +41,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//mockito
testImplementation 'org.mockito:mockito-inline:2.13.0'
//Retrofit and GSON
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
@@ -56,11 +62,7 @@ dependencies {
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
//Android Room
implementation "androidx.room:room-runtime:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
//recycler view item data binding
implementation 'com.xwray:groupie:2.3.0'
implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0'
implementation 'com.xwray:groupie-databinding:2.3.0'

View File

@@ -3,5 +3,5 @@ package com.example.h_mal.candyspace.ui.main
interface CompletionListener {
fun onStarted()
fun onSuccess()
fun onFailure()
fun onFailure(message: String)
}

View File

@@ -1,14 +1,28 @@
package com.example.h_mal.candyspace.ui.main
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.example.h_mal.candyspace.R
import com.example.h_mal.candyspace.databinding.MainFragmentBinding
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
class MainFragment : Fragment() {
/*
* UI for the first screen holding the list, search box
*/
class MainFragment : Fragment(), KodeinAware{
//retrieve the viewmodel factory from the kodein dependency injection
override val kodein by lazy { (context as KodeinAware).kodein }
private val factory : MainViewModelFactory by instance()
companion object {
fun newInstance() = MainFragment()
@@ -18,13 +32,20 @@ class MainFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
//retrieve viewmodel from viewmodel factory
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
//Bind layout to Viewmodel
val binding: MainFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
binding.viewmodel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
// TODO: Use the ViewModel
}
}

View File

@@ -3,6 +3,10 @@ package com.example.h_mal.candyspace.ui.main
import android.view.View
import androidx.lifecycle.ViewModel
import com.example.h_mal.candyspace.data.repositories.Repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
class MainViewModel(
private val repository: Repository
@@ -10,7 +14,26 @@ class MainViewModel(
var searchString: String? = null
fun submit(view: View){
val completionListener: CompletionListener? = null
fun submit(view: View){
completionListener?.onStarted()
if (searchString.isNullOrEmpty()){
completionListener?.onFailure("Search box is empty")
return
}
CoroutineScope(Dispatchers.Main).launch {
try {
val user = repository.getUsers(searchString!!)
//todo: update live data
//successfully finished
completionListener?.onSuccess()
}catch (e: IOException){
completionListener?.onFailure(e.message!!)
}
completionListener?.onFailure("Failed to retrieve Users")
}
}
}

View File

@@ -0,0 +1,15 @@
package com.example.h_mal.candyspace.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.h_mal.candyspace.data.repositories.Repository
@Suppress("UNCHECKED_CAST")
class MainViewModelFactory(
private val repository: Repository
) : ViewModelProvider.NewInstanceFactory(){
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}

View File

@@ -1,47 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.MainFragment">
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
<data>
<variable
name="viewmodel"
type="com.example.h_mal.candyspace.ui.main.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="4dp"
android:orientation="horizontal"
android:id="@+id/container">
<EditText
android:id="@+id/search_bar"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/submit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:context=".ui.main.MainFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/search_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Submit"/>
</LinearLayout>
android:layout_margin="4dp"
android:orientation="horizontal"
android:id="@+id/container">
<EditText
android:id="@+id/search_bar"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<view class="androidx.appcompat.app.AlertController$RecycleListView"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/container"
tools:listitem="@android:layout/simple_list_item_2">
<Button
android:id="@+id/submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/search_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Submit"/>
</LinearLayout>
</view>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/container"
tools:listitem="@android:layout/simple_list_item_2">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -1,12 +1,65 @@
package com.example.h_mal.candyspace.data.repositories
import android.util.Log
import com.example.h_mal.candyspace.data.api.ApiClass
import com.example.h_mal.candyspace.data.api.User
import com.google.gson.Gson
import kotlinx.coroutines.runBlocking
import okhttp3.MediaType
import okhttp3.ResponseBody
import org.junit.Before
import org.junit.Assert.*
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import retrofit2.Response
class RepositoryTest {
lateinit var repository: Repository
@Mock
lateinit var api: ApiClass
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = Repository(api)
}
@Test
fun fetchUserFromApi_positiveResponse() = runBlocking {
val mockUser = mock(User::class.java)
val mockResponse = Response.success(mockUser)
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
mockResponse
)
val getUser = repository.getUsers("12345")
assertNotNull(getUser)
assertEquals(mockUser, getUser)
}
@Test
fun fetchUserFromApi_negativeResponse() = runBlocking {
val mockResponse = Response.error<User>(403,
ResponseBody.create(
MediaType.parse("application/json"),
"{\"key\":[\"somestuff\"]}"
))
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
mockResponse
)
val getUser = repository.getUsers("12345")
assertNotNull(getUser)
}
}