From 6d45b0bfae896c4e38f112ce26e0ca731b05caa7 Mon Sep 17 00:00:00 2001 From: norangebit Date: Wed, 4 Dec 2019 18:35:17 +0100 Subject: [PATCH] complete test BoardRepository - refactoring BoardRepository for testing --- .../app/data/repository/BoardRepository.kt | 44 +-- .../ids/wedroid/app/BoardRepositoryTest.kt | 171 ---------- .../ding/ids/wedroid/app/TestHelper.kt | 13 + .../data/repository/BoardRepositoryTest.kt | 318 ++++++++++++++++++ 4 files changed, 355 insertions(+), 191 deletions(-) delete mode 100644 app/src/test/java/it/unisannio/ding/ids/wedroid/app/BoardRepositoryTest.kt create mode 100644 app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt create mode 100644 app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt diff --git a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt index 92eb29b..204940d 100644 --- a/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt +++ b/app/src/main/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepository.kt @@ -41,11 +41,15 @@ class BoardRepository( override fun onResponse( call: Call>, response: Response> - ) = synchronizeCallback(response) + ) { + CoroutineScope(Dispatchers.IO).launch { + synchronizeCallback(response) + } + } }) } - private fun synchronizeCallback( + private suspend fun synchronizeCallback( response: Response> ) { if (!response.isSuccessful) { @@ -81,11 +85,15 @@ class BoardRepository( override fun onResponse( call: Call, response: Response - ) = insertBoardCallback(response, title) + ) { + CoroutineScope(Dispatchers.IO).launch { + insertBoardCallback(response, title) + } + } }) } - private fun insertBoardCallback( + private suspend fun insertBoardCallback( response: Response, title: String ) { @@ -101,9 +109,7 @@ class BoardRepository( return } - CoroutineScope(Dispatchers.IO).launch { - dao.insert(Board(board.id, title)) - } + dao.insert(Board(board.id, title)) } fun deleteBoard(id: String) { @@ -116,38 +122,36 @@ class BoardRepository( override fun onResponse( call: Call, response: Response - ) = deleteBoardCallback(response, id) + ) { + CoroutineScope(Dispatchers.IO).launch { + deleteBoardCallback(response, id) + } + } }) } - private fun deleteBoardCallback( + private suspend fun deleteBoardCallback( response: Response, id: String ) { if (!response.isSuccessful) { - logNetworkError("${response.code()}, ${response.message()}") + logNetworkError("${response.code()} ${response.message()}") return } - CoroutineScope(Dispatchers.IO).launch { - dao.delete(Board(id)) - } + dao.delete(Board(id)) } - private fun addNewBoardToDb(boards: Collection) { + private suspend fun addNewBoardToDb(boards: Collection) { boards.forEach { - CoroutineScope(Dispatchers.IO).launch { - dao.insert(it) - } + dao.insert(it) } } - private fun removeOldBoardsFromDb(boards: Collection) { + private suspend fun removeOldBoardsFromDb(boards: Collection) { allBoards.value?.minus(boards) ?.forEach { - CoroutineScope(Dispatchers.IO).launch { dao.delete(it) - } } } diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/BoardRepositoryTest.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/BoardRepositoryTest.kt deleted file mode 100644 index 3e73d76..0000000 --- a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/BoardRepositoryTest.kt +++ /dev/null @@ -1,171 +0,0 @@ -package it.unisannio.ding.ids.wedroid.app - -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao -import it.unisannio.ding.ids.wedroid.app.data.entity.Board -import it.unisannio.ding.ids.wedroid.app.data.repository.BoardRepository -import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader -import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService -import it.unisannio.ding.ids.wedroid.wrapper.entity.BoardBackgroundColor -import junit.framework.TestCase.* -import kotlinx.coroutines.runBlocking -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before -import org.junit.Test -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.net.HttpURLConnection -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.jvm.isAccessible - -class BoardRepositoryTest { - private val reader = mockk() - private val webServer = MockWebServer() - private lateinit var service: BoardService - private lateinit var latch: CountDownLatch - - @Before - fun setUp() { - webServer.start() - service = Retrofit.Builder() - .baseUrl(webServer.url("/")) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(BoardService::class.java) - - every { reader.userId } returns "user id" - - - latch = CountDownLatch(1) - } - - @After - fun teardown() { - webServer.close() - } - - @Test - fun insertWithOkOnServer() { - val dao = mockk() - var inInsert = false - - coEvery { - dao.insert(any()) - } answers { - inInsert = true - assertEquals("id", arg(0).id) - assertEquals("title", arg(0).title) - latch.countDown() - } - - webServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody("{ \"_id\": \"id\" }") - ) - - val repository = BoardRepository( - dao, service, reader - ) - - repository.insertBoard( - "title", true, BoardBackgroundColor.LIMEGREEN - ) - - latch.await(5, TimeUnit.SECONDS) - - assertTrue(inInsert) - } - - @Test - fun notInsertWithErrorOnServer() { - val dao = mockk() - var inInsert = false - - coEvery { - dao.insert(any() as Board) - } answers { - inInsert = true - latch.countDown() - } - - webServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST) - ) - - val repository = BoardRepository( - dao, service, reader - ) - - repository.insertBoard( - "title", true, BoardBackgroundColor.LIMEGREEN - ) - - latch.await(3, TimeUnit.SECONDS) - - assertFalse(inInsert) - } - - @Test - fun deleteWithOkOnServer() { - val dao = mockk() - var inDelete = false - - coEvery { - dao.delete(any() as Board) - } answers { - assertEquals("id", arg(0).id) - inDelete = true - latch.countDown() - } - - webServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - ) - - val repository = BoardRepository( - dao, service, reader - ) - - repository.deleteBoard("id") - - latch.await(5, TimeUnit.SECONDS) - assertTrue(inDelete) - } - - @Test - fun notDeleteWithErrorOnServer() { - val dao = mockk() - var inDelete = false - - coEvery { - dao.delete(any() as Board) - } answers { - assertEquals("id", arg(0).id) - inDelete = true - latch.countDown() - } - - webServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST) - ) - - val repository = BoardRepository( - dao, service, reader - ) - - repository.deleteBoard("id") - - latch.await(3, TimeUnit.SECONDS) - assertFalse(inDelete) - } -} \ No newline at end of file diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt new file mode 100644 index 0000000..42a2ada --- /dev/null +++ b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/TestHelper.kt @@ -0,0 +1,13 @@ +package it.unisannio.ding.ids.wedroid.app + +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.jvm.isAccessible + +fun getPrivateFun(name: String, kClass: KClass<*>): KFunction<*>? { + return kClass.declaredFunctions + .find { it.name == name } + .also { it?.isAccessible = true } +} + diff --git a/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt new file mode 100644 index 0000000..d8def75 --- /dev/null +++ b/app/src/test/java/it/unisannio/ding/ids/wedroid/app/data/repository/BoardRepositoryTest.kt @@ -0,0 +1,318 @@ +package it.unisannio.ding.ids.wedroid.app.data.repository + +import android.util.Log +import androidx.lifecycle.LiveData +import io.mockk.* +import it.unisannio.ding.ids.wedroid.app.data.dao.BoardDao +import it.unisannio.ding.ids.wedroid.app.data.entity.Board +import it.unisannio.ding.ids.wedroid.app.getPrivateFun +import it.unisannio.ding.ids.wedroid.app.util.PreferenceReader +import it.unisannio.ding.ids.wedroid.wrapper.api.BoardService +import junit.framework.TestCase.* +import kotlinx.coroutines.* +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before +import org.junit.Test +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import kotlin.reflect.full.callSuspend + +class BoardRepositoryTest { + private val reader = mockk() + private val webServer = MockWebServer() + private lateinit var service: BoardService + + @Before + fun setUp() { + webServer.start() + service = Retrofit.Builder() + .baseUrl(webServer.url("/")) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(BoardService::class.java) + + mockkStatic(Log::class) + every { reader.userId } returns "user id" + every { Log.e(any(), any()) } returns 0 + } + + @After + fun teardown() { + webServer.close() + } + + @Test + fun addNewBoardsToDb() { + val dao = mockk() + var count = 0 + + coEvery { + dao.insert(any()) + } answers { + count++ + } + + val addNewBoardToDb = getPrivateFun( + "addNewBoardToDb", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + val board0 = Board("id0", "title0") + val board1 = Board("id1", "title1") + val board2 = Board("id2", "title2") + + runBlocking { + addNewBoardToDb?.callSuspend( + repository, + listOf(board0, board1, board2) + ) + } + + coVerifyAll { + dao.insert(board0) + dao.insert(board1) + dao.insert(board2) + } + + assertEquals(3, count) + } + + @Test + fun insertBoardCallbackSuccess() { + val dao = mockk() + val response = + mockk>() + val board = mockk() + + every { response.isSuccessful } returns true + every { response.body() } returns board + every { board.id } returns "id" + coEvery { dao.insert(any()) } answers {} + + val insertBoard = getPrivateFun( + "insertBoardCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + insertBoard?.callSuspend( + repository, + response, + "title" + ) + } + + coVerify { dao.insert(Board("id", "title")) } + } + + @Test + fun insertBoardCallbackEmptyBody() { + val dao = mockk() + val response = + mockk>() + + every { response.isSuccessful } returns true + every { response.body() } returns null + + val insertBoard = getPrivateFun( + "insertBoardCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + insertBoard?.callSuspend( + repository, + response, + "title" + ) + } + + verify { Log.e("RETROFIT", "empty body") } + } + + @Test + fun insertBoardCallbackError() { + val dao = mockk() + val response = + mockk>() + + every { response.isSuccessful } returns false + every { response.code() } returns 400 + every { response.message() } returns "Error" + + val insertBoard = getPrivateFun( + "insertBoardCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + insertBoard?.callSuspend( + repository, + response, + "title" + ) + } + + verify { Log.e("RETROFIT", "400 Error") } + } + + @Test + fun deleteBoardCallbackSuccess() { + val dao = mockk() + val response = + mockk>() + + every { response.isSuccessful } returns true + coEvery { dao.delete(any()) } answers {} + + val deleteBoard = getPrivateFun( + "deleteBoardCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + deleteBoard?.callSuspend( + repository, + response, + "id" + ) + } + + coVerify { dao.delete(Board("id")) } + } + + @Test + fun deleteBoardCallbackError() { + val dao = mockk() + val response = + mockk>() + + every { response.isSuccessful } returns false + every { response.code() } returns 400 + every { response.message() } returns "Error" + + val deleteBoard = getPrivateFun( + "deleteBoardCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + deleteBoard?.callSuspend( + repository, + response, + "id" + ) + } + + verify { Log.e("RETROFIT", "400 Error") } + } + + @Test + fun deleteOldBoardFromDb() { + val dao = mockk() + val dbBoards = mockk>>() + + val board0 = Board("id0", "title0") + val board1 = Board("id1", "title1") + val board2 = Board("id2", "title2") + val board3 = Board("id2", "title3") + + every { dbBoards.value } returns listOf( + board0, board1, board2, board3 + ) + coEvery { dao.getAllBoard() } returns dbBoards + coEvery { dao.delete(any()) } answers {} + + val removeOldBoards = getPrivateFun( + "removeOldBoardsFromDb", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + removeOldBoards?.callSuspend( + repository, + listOf( + board0, board1, board3 + ) + ) + } + + coVerify { dao.delete(board2) } + } + + @Test + fun synchronizeCallbackError() { + val dao = mockk() + val response = + mockk>>() + + every { response.isSuccessful } returns false + every { response.code() } returns 400 + every { response.message() } returns "Error" + + val synchronize = getPrivateFun( + "synchronizeCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + synchronize?.callSuspend( + repository, + response + ) + } + + verify { Log.e("RETROFIT", "400 Error") } + } + + @Test + fun synchronizeCallbackEmptyBody() { + val dao = mockk() + val response = + mockk>>() + + every { response.isSuccessful } returns true + every { response.body() } returns null + + val synchronize = getPrivateFun( + "synchronizeCallback", BoardRepository::class + ) + + val repository = BoardRepository( + dao, service, reader + ) + + runBlocking { + synchronize?.callSuspend( + repository, + response + ) + } + } +} \ No newline at end of file