diff --git a/app/build.gradle b/app/build.gradle index 2c388c7..de272f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,6 +58,10 @@ dependencies { //mock websever for testing retrofit responses testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + // Mockk + def mockk_ver = "1.10.2" + testImplementation "io.mockk:mockk:$mockk_ver" + androidTestImplementation "io.mockk:mockk-android:$mockk_ver" //mockito and livedata testing testImplementation 'org.mockito:mockito-inline:2.13.0' @@ -90,4 +94,7 @@ dependencies { // Circle Image View implementation 'com.mikhaellopez:circularimageview:4.2.0' + + // extra + testImplementation 'org.json:json:20180813' } \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/h_mal/movielisttest/data/room/MoviesRoomDatabaseTest.kt b/app/src/androidTest/java/com/example/h_mal/movielisttest/data/room/MoviesRoomDatabaseTest.kt index 85c23da..086ddca 100644 --- a/app/src/androidTest/java/com/example/h_mal/movielisttest/data/room/MoviesRoomDatabaseTest.kt +++ b/app/src/androidTest/java/com/example/h_mal/movielisttest/data/room/MoviesRoomDatabaseTest.kt @@ -13,6 +13,7 @@ import org.junit.Test import org.junit.runner.RunWith import java.io.IOException + @RunWith(AndroidJUnit4::class) class MoviesRoomDatabaseTest{ private lateinit var simpleDao: SimpleDao diff --git a/app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/ResponseUnwrap.kt b/app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCall.kt similarity index 96% rename from app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/ResponseUnwrap.kt rename to app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCall.kt index f49b633..6f84666 100644 --- a/app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/ResponseUnwrap.kt +++ b/app/src/main/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCall.kt @@ -9,7 +9,7 @@ import java.io.IOException * Abstract class for extracting from Retrofit Response * Or throw IOException if the API call fails */ -abstract class ResponseUnwrap { +abstract class SafeApiCall { @Suppress("BlockingMethodInNonBlockingContext") suspend fun responseUnwrap( diff --git a/app/src/main/java/com/example/h_mal/movielisttest/data/repository/RepositoryImpl.kt b/app/src/main/java/com/example/h_mal/movielisttest/data/repository/RepositoryImpl.kt index 91bf5a0..1c8dbc2 100644 --- a/app/src/main/java/com/example/h_mal/movielisttest/data/repository/RepositoryImpl.kt +++ b/app/src/main/java/com/example/h_mal/movielisttest/data/repository/RepositoryImpl.kt @@ -1,7 +1,7 @@ package com.example.h_mal.movielisttest.data.repository import com.example.h_mal.movielisttest.data.network.MoviesApi -import com.example.h_mal.movielisttest.data.network.networkUtils.ResponseUnwrap +import com.example.h_mal.movielisttest.data.network.networkUtils.SafeApiCall import com.example.h_mal.movielisttest.data.network.response.MoviesResponse import com.example.h_mal.movielisttest.data.network.response.ResultsItem import com.example.h_mal.movielisttest.data.prefs.PreferenceProvider @@ -12,7 +12,7 @@ class RepositoryImpl( private val api: MoviesApi, private val database: MoviesRoomDatabase, private val preferences: PreferenceProvider -) : Repository, ResponseUnwrap() { +) : Repository, SafeApiCall() { override suspend fun getMoviesFromApi(pageNumber: Int): MoviesResponse? { diff --git a/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MainFragment.kt b/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MainFragment.kt index f5f6ff0..fd01f98 100644 --- a/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MainFragment.kt +++ b/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MainFragment.kt @@ -7,7 +7,6 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer -import androidx.recyclerview.widget.SimpleItemAnimator import com.example.h_mal.movielisttest.R import com.example.h_mal.movielisttest.utils.* import kotlinx.android.synthetic.main.empty_view_item.view.* diff --git a/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MoviesRecyclerViewAdapter.kt b/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MoviesRecyclerViewAdapter.kt index 548ceda..aa11ad3 100644 --- a/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MoviesRecyclerViewAdapter.kt +++ b/app/src/main/java/com/example/h_mal/movielisttest/ui/main/MoviesRecyclerViewAdapter.kt @@ -9,7 +9,6 @@ import androidx.recyclerview.widget.RecyclerView import com.example.h_mal.movielisttest.R import com.example.h_mal.movielisttest.data.models.Movie import com.example.h_mal.movielisttest.utils.loadImage -import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.item_layout.view.* /** diff --git a/app/src/main/java/com/example/h_mal/movielisttest/utils/ViewUtils.kt b/app/src/main/java/com/example/h_mal/movielisttest/utils/ViewUtils.kt index 061cd50..c8a2b49 100644 --- a/app/src/main/java/com/example/h_mal/movielisttest/utils/ViewUtils.kt +++ b/app/src/main/java/com/example/h_mal/movielisttest/utils/ViewUtils.kt @@ -5,7 +5,6 @@ import android.view.View import android.widget.ImageView import android.widget.Toast import androidx.recyclerview.widget.RecyclerView -import com.example.h_mal.movielisttest.R import com.squareup.picasso.Picasso fun View.show() { diff --git a/app/src/test/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCallTest.kt b/app/src/test/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCallTest.kt new file mode 100644 index 0000000..55b458e --- /dev/null +++ b/app/src/test/java/com/example/h_mal/movielisttest/data/network/networkUtils/SafeApiCallTest.kt @@ -0,0 +1,52 @@ +package com.example.h_mal.movielisttest.data.network.networkUtils + +import com.example.h_mal.movielisttest.data.network.response.MoviesResponse +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test +import retrofit2.Response +import java.io.IOException +import kotlin.test.assertFailsWith + +class SafeApiCallTest: SafeApiCall(){ + + @Test + fun successfulResponse_SuccessfulOutput() = runBlocking{ + // GIVEN + val mockApiResponse = mockk() + val mockResponse = Response.success(mockApiResponse) + + // WHEN + val result = responseUnwrap { mockResponse } + + // THEN + assertNotNull(result) + assertEquals(mockApiResponse, result) + } + + @Test + fun unsuccessfulResponse_thrownOutput() = runBlocking{ + // GIVEN + val errorMessage = "{\n" + + " \"status_code\": 7,\n" + + " \"status_message\": \"Invalid API key: You must be granted a valid key.\",\n" + + " \"success\": false\n" + + "}" + + val errorResponseBody = errorMessage.toResponseBody("application/json".toMediaTypeOrNull()) + val mockResponse = Response.error(404, errorResponseBody) + + //THEN - assert exception is not null + val ioExceptionReturned = assertFailsWith { + responseUnwrap { mockResponse }!! + } + + assertNotNull(ioExceptionReturned) + assertEquals(ioExceptionReturned.message, "Invalid API key: You must be granted a valid key.") + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/example/h_mal/movielisttest/data/repository/RepositoryImplTest.kt b/app/src/test/java/com/example/h_mal/movielisttest/data/repository/RepositoryImplTest.kt index 68257b2..77aacbb 100644 --- a/app/src/test/java/com/example/h_mal/movielisttest/data/repository/RepositoryImplTest.kt +++ b/app/src/test/java/com/example/h_mal/movielisttest/data/repository/RepositoryImplTest.kt @@ -6,7 +6,8 @@ import com.example.h_mal.movielisttest.data.prefs.PreferenceProvider import com.example.h_mal.movielisttest.data.room.MoviesRoomDatabase import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test import org.mockito.Mock diff --git a/app/src/test/java/com/example/h_mal/movielisttest/ui/main/MainViewModelTest.kt b/app/src/test/java/com/example/h_mal/movielisttest/ui/main/MainViewModelTest.kt index 4118d99..2c6ad39 100644 --- a/app/src/test/java/com/example/h_mal/movielisttest/ui/main/MainViewModelTest.kt +++ b/app/src/test/java/com/example/h_mal/movielisttest/ui/main/MainViewModelTest.kt @@ -2,16 +2,12 @@ package com.example.h_mal.movielisttest.ui.main import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import androidx.test.espresso.idling.CountingIdlingResource -import com.example.h_mal.movielisttest.application.MovieListApplication.Companion.idlingResources import com.example.h_mal.movielisttest.data.network.response.MoviesResponse import com.example.h_mal.movielisttest.data.repository.Repository import com.example.h_mal.movielisttest.data.room.MovieEntity import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking -import org.junit.Assert.* +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -20,7 +16,8 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations import java.io.IOException -import javax.annotation.meta.When + + class MainViewModelTest { @@ -32,9 +29,6 @@ class MainViewModelTest { @Mock lateinit var repository: Repository - @Mock - lateinit var observer: Observer> - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -55,12 +49,9 @@ class MainViewModelTest { //THEN viewModel.loadMovies() - delay(200) - viewModel.operationState.observeForever{ - it.getContentIfNotHandled()?.let {result -> - kotlin.test.assertFalse { result } - } - } + delay(50) + + kotlin.test.assertFalse { viewModel.operationState.value?.getContentIfNotHandled()!! } } @Test @@ -70,11 +61,8 @@ class MainViewModelTest { // THEN viewModel.loadMovies() - viewModel.operationError.observeForever{ - it.getContentIfNotHandled()?.let {result -> - assertEquals(result, "throwed") - } - } + delay(50) + assertEquals(viewModel.operationError.value?.getContentIfNotHandled()!!, "throwed") } @Test @@ -83,30 +71,25 @@ class MainViewModelTest { val mockApiResponse = Mockito.mock(MoviesResponse::class.java) //WHEN - Mockito.`when`(repository.getMoviesFromApi(2)).thenReturn(mockApiResponse) Mockito.`when`(repository.getCurrentPage()).thenReturn(2) + Mockito.`when`(repository.getMoviesFromApi(2)).thenReturn(mockApiResponse) //THEN - viewModel.loadMovies() - delay(200) - viewModel.operationState.observeForever{ - it.getContentIfNotHandled()?.let {result -> - kotlin.test.assertFalse { result } - } - } + viewModel.loadMoreMovies() + delay(50) + kotlin.test.assertFalse { viewModel.operationState.value?.getContentIfNotHandled()!! } } @Test fun getMoreFromRepository_unsuccessfulReturn() = runBlocking{ // WHEN + Mockito.`when`(repository.getCurrentPage()).thenReturn(2) Mockito.`when`(repository.getMoviesFromApi(2)).thenAnswer{ throw IOException("throwed") } // THEN - viewModel.loadMovies() - viewModel.operationError.observeForever{ - it.getContentIfNotHandled()?.let {result -> - assertEquals(result, "throwed") - } - } + viewModel.loadMoreMovies() + delay(50) + assertEquals(viewModel.operationError.value?.getContentIfNotHandled()!!, "throwed") + } } \ No newline at end of file