mirror of
https://github.com/hmalik144/Candy_Space_tech_test.git
synced 2025-12-10 03:05:27 +00:00
View binding added, basic repository unit tests added
This commit is contained in:
@@ -24,6 +24,10 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -37,6 +41,8 @@ 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
|
||||||
|
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||||
|
|
||||||
//Retrofit and GSON
|
//Retrofit and GSON
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
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-generic-jvm:6.2.1"
|
||||||
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
||||||
|
|
||||||
//Android Room
|
//recycler view item data binding
|
||||||
implementation "androidx.room:room-runtime:2.2.5"
|
|
||||||
implementation "androidx.room:room-ktx:2.2.5"
|
|
||||||
kapt "androidx.room:room-compiler:2.2.5"
|
|
||||||
|
|
||||||
implementation 'com.xwray:groupie:2.3.0'
|
implementation 'com.xwray:groupie:2.3.0'
|
||||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0'
|
implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0'
|
||||||
implementation 'com.xwray:groupie-databinding:2.3.0'
|
implementation 'com.xwray:groupie-databinding:2.3.0'
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package com.example.h_mal.candyspace.ui.main
|
|||||||
interface CompletionListener {
|
interface CompletionListener {
|
||||||
fun onStarted()
|
fun onStarted()
|
||||||
fun onSuccess()
|
fun onSuccess()
|
||||||
fun onFailure()
|
fun onFailure(message: String)
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,28 @@
|
|||||||
package com.example.h_mal.candyspace.ui.main
|
package com.example.h_mal.candyspace.ui.main
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
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 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.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 {
|
companion object {
|
||||||
fun newInstance() = MainFragment()
|
fun newInstance() = MainFragment()
|
||||||
@@ -18,13 +32,20 @@ class MainFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View {
|
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?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
|
|
||||||
// TODO: Use the ViewModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package com.example.h_mal.candyspace.ui.main
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.example.h_mal.candyspace.data.repositories.Repository
|
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(
|
class MainViewModel(
|
||||||
private val repository: Repository
|
private val repository: Repository
|
||||||
@@ -10,7 +14,26 @@ class MainViewModel(
|
|||||||
|
|
||||||
var searchString: String? = null
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +1,56 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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">
|
|
||||||
|
|
||||||
<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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
tools:context=".ui.main.MainFragment">
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
<LinearLayout
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
android:layout_width="match_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="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintStart_toEndOf="@id/search_bar"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:text="Submit"/>
|
android:layout_margin="4dp"
|
||||||
</LinearLayout>
|
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"
|
<Button
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/submit"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintStart_toEndOf="@id/search_bar"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/container"
|
android:text="Submit"/>
|
||||||
tools:listitem="@android:layout/simple_list_item_2">
|
</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>
|
||||||
|
|||||||
@@ -1,12 +1,65 @@
|
|||||||
package com.example.h_mal.candyspace.data.repositories
|
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.Before
|
||||||
|
|
||||||
import org.junit.Assert.*
|
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 {
|
class RepositoryTest {
|
||||||
|
|
||||||
|
lateinit var repository: Repository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var api: ApiClass
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user