Issue#41 Copy full example into separate module with Hilt Integration

This commit is contained in:
Gergely Hegedus 2022-09-27 17:16:05 +03:00
parent 69e76dc0da
commit 52a99a82fc
229 changed files with 8416 additions and 11 deletions

View file

@ -0,0 +1,59 @@
package org.fnives.test.showcase.hilt.core.content
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.storage.content.FavouriteContentLocalStorage
import org.fnives.test.showcase.model.content.ContentId
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class AddContentToFavouriteUseCaseTest {
private lateinit var sut: AddContentToFavouriteUseCase
private lateinit var mockFavouriteContentLocalStorage: FavouriteContentLocalStorage
@BeforeEach
fun setUp() {
mockFavouriteContentLocalStorage = mock()
sut = AddContentToFavouriteUseCase(mockFavouriteContentLocalStorage)
}
@DisplayName("WHEN nothing happens THEN the storage is not touched")
@Test
fun initializationDoesntAffectStorage() {
verifyNoInteractions(mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN contentId WHEN called THEN storage is called")
@Test
fun contentIdIsDelegatedToStorage() = runTest {
sut.invoke(ContentId("a"))
verify(mockFavouriteContentLocalStorage, times(1)).markAsFavourite(ContentId("a"))
verifyNoMoreInteractions(mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN throwing local storage WHEN thrown THEN its propagated")
@Test
fun storageThrowingIsPropagated() = runTest {
whenever(mockFavouriteContentLocalStorage.markAsFavourite(ContentId("a"))).doThrow(
RuntimeException()
)
assertThrows(RuntimeException::class.java) {
runBlocking { sut.invoke(ContentId("a")) }
}
}
}

View file

@ -0,0 +1,152 @@
package org.fnives.test.showcase.hilt.core.content
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.shared.UnexpectedException
import org.fnives.test.showcase.hilt.network.content.ContentRemoteSource
import org.fnives.test.showcase.model.content.Content
import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.model.content.ImageUrl
import org.fnives.test.showcase.model.shared.Resource
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doSuspendableAnswer
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class ContentRepositoryTest {
private lateinit var sut: ContentRepository
private lateinit var mockContentRemoteSource: ContentRemoteSource
@BeforeEach
fun setUp() {
mockContentRemoteSource = mock()
sut = ContentRepository(mockContentRemoteSource)
}
@DisplayName("GIVEN no interaction THEN remote source is not called")
@Test
fun fetchingIsLazy() {
verifyNoMoreInteractions(mockContentRemoteSource)
}
@DisplayName("GIVEN content response WHEN content observed THEN loading AND data is returned")
@Test
fun happyFlow() = runTest {
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl(""))))
)
whenever(mockContentRemoteSource.get()).doReturn(
listOf(Content(ContentId("a"), "", "", ImageUrl("")))
)
val actual = sut.contents.take(2).toList()
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN content error WHEN content observed THEN loading AND data is returned")
@Test
fun errorFlow() = runTest {
val exception = RuntimeException()
val expected = listOf(
Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception))
)
whenever(mockContentRemoteSource.get()).doThrow(exception)
val actual = sut.contents.take(2).toList()
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN saved cache WHEN collected THEN cache is returned")
@Test
fun verifyCaching() = runTest {
val content = Content(ContentId("1"), "", "", ImageUrl(""))
val expected = listOf(Resource.Success(listOf(content)))
whenever(mockContentRemoteSource.get()).doReturn(listOf(content))
sut.contents.take(2).toList()
val actual = sut.contents.take(1).toList()
verify(mockContentRemoteSource, times(1)).get()
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN no response from remote source WHEN content observed THEN loading is returned")
@Test
fun loadingIsShownBeforeTheRequestIsReturned() = runTest {
val expected = Resource.Loading<List<Content>>()
val suspendedRequest = CompletableDeferred<Unit>()
whenever(mockContentRemoteSource.get()).doSuspendableAnswer {
suspendedRequest.await()
emptyList()
}
val actual = sut.contents.take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
suspendedRequest.complete(Unit)
}
@DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error")
@Test
fun whenFetchingRequestIsCalledAgain() = runTest() {
val exception = RuntimeException()
val expected = listOf(
Resource.Loading(),
Resource.Success(emptyList()),
Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception))
)
var first = true
whenever(mockContentRemoteSource.get()).doAnswer {
if (first) emptyList<Content>().also { first = false } else throw exception
}
val actual = async {
sut.contents.take(4).toList()
}
advanceUntilIdle()
sut.fetch()
advanceUntilIdle()
Assertions.assertEquals(expected, actual.getCompleted())
}
@DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted")
@Test
fun noAdditionalItemsEmitted() = runTest {
val exception = RuntimeException()
var first = true
whenever(mockContentRemoteSource.get()).doAnswer {
if (first) emptyList<Content>().also { first = false } else throw exception
}
val actual = async(coroutineContext) { sut.contents.take(5).toList() }
advanceUntilIdle()
sut.fetch()
advanceUntilIdle()
Assertions.assertFalse(actual.isCompleted)
actual.cancel()
}
}

View file

@ -0,0 +1,55 @@
package org.fnives.test.showcase.hilt.core.content
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class FetchContentUseCaseTest {
private lateinit var sut: FetchContentUseCase
private lateinit var mockContentRepository: ContentRepository
@BeforeEach
fun setUp() {
mockContentRepository = mock()
sut = FetchContentUseCase(mockContentRepository)
}
@DisplayName("WHEN nothing happens THEN the storage is not touched")
@Test
fun initializationDoesntAffectRepository() {
verifyNoInteractions(mockContentRepository)
}
@DisplayName("WHEN called THEN repository is called")
@Test
fun whenCalledRepositoryIsFetched() = runTest {
sut.invoke()
verify(mockContentRepository, times(1)).fetch()
verifyNoMoreInteractions(mockContentRepository)
}
@DisplayName("GIVEN throwing local storage WHEN thrown THEN its thrown")
@Test
fun whenRepositoryThrowsUseCaseAlsoThrows() = runTest {
whenever(mockContentRepository.fetch()).doThrow(RuntimeException())
assertThrows(RuntimeException::class.java) {
runBlocking { sut.invoke() }
}
}
}

View file

@ -0,0 +1,222 @@
package org.fnives.test.showcase.hilt.core.content
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.storage.content.FavouriteContentLocalStorage
import org.fnives.test.showcase.model.content.Content
import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.model.content.FavouriteContent
import org.fnives.test.showcase.model.content.ImageUrl
import org.fnives.test.showcase.model.shared.Resource
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class GetAllContentUseCaseTest {
private lateinit var sut: GetAllContentUseCase
private lateinit var mockContentRepository: ContentRepository
private lateinit var mockFavouriteContentLocalStorage: FavouriteContentLocalStorage
private lateinit var contentFlow: MutableStateFlow<Resource<List<Content>>>
private lateinit var favouriteContentIdFlow: MutableStateFlow<List<ContentId>>
@BeforeEach
fun setUp() {
mockFavouriteContentLocalStorage = mock()
mockContentRepository = mock()
favouriteContentIdFlow = MutableStateFlow(emptyList())
contentFlow = MutableStateFlow(Resource.Loading())
whenever(mockFavouriteContentLocalStorage.observeFavourites()).doReturn(
favouriteContentIdFlow
)
whenever(mockContentRepository.contents).doReturn(contentFlow)
sut = GetAllContentUseCase(mockContentRepository, mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN loading AND empty favourite WHEN observed THEN loading is shown")
@Test
fun loadingResourceWithNoFavouritesResultsInLoadingResource() = runTest {
favouriteContentIdFlow.value = emptyList()
contentFlow.value = Resource.Loading()
val expected = Resource.Loading<List<FavouriteContent>>()
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN loading AND listOfFavourite WHEN observed THEN loading is shown")
@Test
fun loadingResourceWithFavouritesResultsInLoadingResource() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
contentFlow.value = Resource.Loading()
val expected = Resource.Loading<List<FavouriteContent>>()
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN error AND empty favourite WHEN observed THEN error is shown")
@Test
fun errorResourceWithNoFavouritesResultsInErrorResource() = runTest {
favouriteContentIdFlow.value = emptyList()
val exception = Throwable()
contentFlow.value = Resource.Error(exception)
val expected = Resource.Error<List<FavouriteContent>>(exception)
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN error AND listOfFavourite WHEN observed THEN error is shown")
@Test
fun errorResourceWithFavouritesResultsInErrorResource() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("b"))
val exception = Throwable()
contentFlow.value = Resource.Error(exception)
val expected = Resource.Error<List<FavouriteContent>>(exception)
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN listOfContent AND empty favourite WHEN observed THEN favourites are returned")
@Test
fun successResourceWithNoFavouritesResultsInNoFavouritedItems() = runTest {
favouriteContentIdFlow.value = emptyList()
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, false)
)
val expected = Resource.Success(items)
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN listOfContent AND other favourite id WHEN observed THEN favourites are returned")
@Test
fun successResourceWithDifferentFavouritesResultsInNoFavouritedItems() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("x"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, false)
)
val expected = Resource.Success(items)
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN listOfContent AND same favourite id WHEN observed THEN favourites are returned")
@Test
fun successResourceWithSameFavouritesResultsInFavouritedItems() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, true)
)
val expected = Resource.Success(items)
val actual = sut.get().take(1).toList()
Assertions.assertEquals(listOf(expected), actual)
}
@DisplayName("GIVEN loading then data then added favourite WHEN observed THEN loading then correct favourites are returned")
@Test
fun whileLoadingAndAddingItemsReactsProperly() = runTest {
favouriteContentIdFlow.value = emptyList()
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, false))),
Resource.Success(listOf(FavouriteContent(content, true)))
)
val actual = async(coroutineContext) {
sut.get().take(3).toList()
}
advanceUntilIdle()
contentFlow.value = Resource.Success(listOf(content))
advanceUntilIdle()
favouriteContentIdFlow.value = listOf(ContentId("a"))
advanceUntilIdle()
Assertions.assertEquals(expected, actual.getCompleted())
}
@DisplayName("GIVEN loading then data then removed favourite WHEN observed THEN loading then correct favourites are returned")
@Test
fun whileLoadingAndRemovingItemsReactsProperly() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, true))),
Resource.Success(listOf(FavouriteContent(content, false)))
)
val actual = async(coroutineContext) {
sut.get().take(3).toList()
}
advanceUntilIdle()
contentFlow.value = Resource.Success(listOf(content))
advanceUntilIdle()
favouriteContentIdFlow.value = emptyList()
advanceUntilIdle()
Assertions.assertEquals(expected, actual.getCompleted())
}
@DisplayName("GIVEN loading then data then loading WHEN observed THEN loading then correct favourites then loading are returned")
@Test
fun loadingThenDataThenLoadingReactsProperly() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, true))),
Resource.Loading()
)
val actual = async(coroutineContext) {
sut.get().take(3).toList()
}
advanceUntilIdle()
contentFlow.value = Resource.Success(listOf(content))
advanceUntilIdle()
contentFlow.value = Resource.Loading()
advanceUntilIdle()
Assertions.assertEquals(expected, actual.getCompleted())
}
}

View file

@ -0,0 +1,57 @@
package org.fnives.test.showcase.hilt.core.content
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.storage.content.FavouriteContentLocalStorage
import org.fnives.test.showcase.model.content.ContentId
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class RemoveContentFromFavouritesUseCaseTest {
private lateinit var sut: RemoveContentFromFavouritesUseCase
private lateinit var mockFavouriteContentLocalStorage: FavouriteContentLocalStorage
@BeforeEach
fun setUp() {
mockFavouriteContentLocalStorage = mock()
sut = RemoveContentFromFavouritesUseCase(mockFavouriteContentLocalStorage)
}
@DisplayName("WHEN nothing happens THEN the storage is not touched")
@Test
fun initializationDoesntAffectStorage() {
verifyNoInteractions(mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN contentId WHEN called THEN storage is called")
@Test
fun givenContentIdCallsStorage() = runTest {
sut.invoke(ContentId("a"))
verify(mockFavouriteContentLocalStorage, times(1)).deleteAsFavourite(ContentId("a"))
verifyNoMoreInteractions(mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN throwing local storage WHEN thrown THEN its propogated")
@Test
fun storageExceptionThrowingIsPropogated() = runTest {
whenever(mockFavouriteContentLocalStorage.deleteAsFavourite(ContentId("a"))).doThrow(RuntimeException())
Assertions.assertThrows(RuntimeException::class.java) {
runBlocking { sut.invoke(ContentId("a")) }
}
}
}

View file

@ -0,0 +1,167 @@
package org.fnives.test.showcase.hilt.core.content
import app.cash.turbine.test
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.shared.UnexpectedException
import org.fnives.test.showcase.hilt.network.content.ContentRemoteSource
import org.fnives.test.showcase.model.content.Content
import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.model.content.ImageUrl
import org.fnives.test.showcase.model.shared.Resource
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doSuspendableAnswer
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
class TurbineContentRepositoryTest {
private lateinit var sut: ContentRepository
private lateinit var mockContentRemoteSource: ContentRemoteSource
@BeforeEach
fun setUp() {
mockContentRemoteSource = mock()
sut = ContentRepository(mockContentRemoteSource)
}
@DisplayName("GIVEN no interaction THEN remote source is not called")
@Test
fun fetchingIsLazy() {
verifyNoMoreInteractions(mockContentRemoteSource)
}
@DisplayName("GIVEN content response WHEN content observed THEN loading AND data is returned")
@Test
fun happyFlow() = runTest {
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl(""))))
)
whenever(mockContentRemoteSource.get()).doReturn(
listOf(Content(ContentId("a"), "", "", ImageUrl("")))
)
sut.contents.test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN content error WHEN content observed THEN loading AND data is returned")
@Test
fun errorFlow() = runTest {
val exception = RuntimeException()
val expected = listOf(
Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception))
)
whenever(mockContentRemoteSource.get()).doThrow(exception)
sut.contents.test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN saved cache WHEN collected THEN cache is returned")
@Test
fun verifyCaching() = runTest {
val content = Content(ContentId("1"), "", "", ImageUrl(""))
val expected = listOf(Resource.Success(listOf(content)))
whenever(mockContentRemoteSource.get()).doReturn(listOf(content))
sut.contents.take(2).toList()
sut.contents.test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
verify(mockContentRemoteSource, times(1)).get()
}
@DisplayName("GIVEN no response from remote source WHEN content observed THEN loading is returned")
@Test
fun loadingIsShownBeforeTheRequestIsReturned() = runTest {
val expected = listOf(Resource.Loading<List<Content>>())
val suspendedRequest = CompletableDeferred<Unit>()
whenever(mockContentRemoteSource.get()).doSuspendableAnswer {
suspendedRequest.await()
emptyList()
}
sut.contents.test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
suspendedRequest.complete(Unit)
}
@DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error")
@Test
fun whenFetchingRequestIsCalledAgain() = runTest(UnconfinedTestDispatcher()) {
val exception = RuntimeException()
val expected = listOf(
Resource.Loading(),
Resource.Success(emptyList()),
Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception))
)
var first = true
whenever(mockContentRemoteSource.get()).doAnswer {
if (first) emptyList<Content>().also { first = false } else throw exception
}
sut.contents.test {
sut.fetch()
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
}
}
@DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted")
@Test
fun noAdditionalItemsEmitted() = runTest {
val exception = RuntimeException()
val expected = listOf(
Resource.Loading(),
Resource.Success(emptyList()),
Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception))
)
var first = true
whenever(mockContentRemoteSource.get()).doAnswer {
if (first) emptyList<Content>().also { first = false } else throw exception
}
sut.contents.test {
sut.fetch()
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
}

View file

@ -0,0 +1,230 @@
package org.fnives.test.showcase.hilt.core.content
import app.cash.turbine.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.storage.content.FavouriteContentLocalStorage
import org.fnives.test.showcase.model.content.Content
import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.model.content.FavouriteContent
import org.fnives.test.showcase.model.content.ImageUrl
import org.fnives.test.showcase.model.shared.Resource
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
class TurbineGetAllContentUseCaseTest {
private lateinit var sut: GetAllContentUseCase
private lateinit var mockContentRepository: ContentRepository
private lateinit var mockFavouriteContentLocalStorage: FavouriteContentLocalStorage
private lateinit var contentFlow: MutableStateFlow<Resource<List<Content>>>
private lateinit var favouriteContentIdFlow: MutableStateFlow<List<ContentId>>
@BeforeEach
fun setUp() {
mockFavouriteContentLocalStorage = mock()
mockContentRepository = mock()
favouriteContentIdFlow = MutableStateFlow(emptyList())
contentFlow = MutableStateFlow(Resource.Loading())
whenever(mockFavouriteContentLocalStorage.observeFavourites()).doReturn(
favouriteContentIdFlow
)
whenever(mockContentRepository.contents).doReturn(contentFlow)
sut = GetAllContentUseCase(mockContentRepository, mockFavouriteContentLocalStorage)
}
@DisplayName("GIVEN loading AND empty favourite WHEN observed THEN loading is shown")
@Test
fun loadingResourceWithNoFavouritesResultsInLoadingResource() = runTest {
favouriteContentIdFlow.value = emptyList()
contentFlow.value = Resource.Loading()
val expected = listOf(Resource.Loading<List<FavouriteContent>>())
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN loading AND listOfFavourite WHEN observed THEN loading is shown")
@Test
fun loadingResourceWithFavouritesResultsInLoadingResource() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
contentFlow.value = Resource.Loading()
val expected = listOf(Resource.Loading<List<FavouriteContent>>())
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN error AND empty favourite WHEN observed THEN error is shown")
@Test
fun errorResourceWithNoFavouritesResultsInErrorResource() = runTest {
favouriteContentIdFlow.value = emptyList()
val exception = Throwable()
contentFlow.value = Resource.Error(exception)
val expected = listOf(Resource.Error<List<FavouriteContent>>(exception))
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN error AND listOfFavourite WHEN observed THEN error is shown")
@Test
fun errorResourceWithFavouritesResultsInErrorResource() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("b"))
val exception = Throwable()
contentFlow.value = Resource.Error(exception)
val expected = listOf(Resource.Error<List<FavouriteContent>>(exception))
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN listOfContent AND empty favourite WHEN observed THEN favourites are returned")
@Test
fun successResourceWithNoFavouritesResultsInNoFavouritedItems() = runTest {
favouriteContentIdFlow.value = emptyList()
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, false)
)
val expected = listOf(Resource.Success(items))
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN listOfContent AND other favourite id WHEN observed THEN favourites are returned")
@Test
fun successResourceWithDifferentFavouritesResultsInNoFavouritedItems() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("x"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, false)
)
val expected = listOf(Resource.Success(items))
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN listOfContent AND same favourite id WHEN observed THEN favourites are returned")
@Test
fun successResourceWithSameFavouritesResultsInFavouritedItems() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Success(listOf(content))
val items = listOf(
FavouriteContent(content, true)
)
val expected = listOf(Resource.Success(items))
sut.get().test {
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN loading then data then added favourite WHEN observed THEN loading then correct favourites are returned")
@Test
fun whileLoadingAndAddingItemsReactsProperly() = runTest {
favouriteContentIdFlow.value = emptyList()
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, false))),
Resource.Success(listOf(FavouriteContent(content, true)))
)
sut.get().test {
contentFlow.value = Resource.Success(listOf(content))
favouriteContentIdFlow.value = listOf(ContentId("a"))
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN loading then data then removed favourite WHEN observed THEN loading then correct favourites are returned")
@Test
fun whileLoadingAndRemovingItemsReactsProperly() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, true))),
Resource.Success(listOf(FavouriteContent(content, false)))
)
sut.get().test {
contentFlow.value = Resource.Success(listOf(content))
favouriteContentIdFlow.value = emptyList()
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
@DisplayName("GIVEN loading then data then loading WHEN observed THEN loading then correct favourites then loading are returned")
@Test
fun loadingThenDataThenLoadingReactsProperly() = runTest {
favouriteContentIdFlow.value = listOf(ContentId("a"))
val content = Content(ContentId("a"), "b", "c", ImageUrl("d"))
contentFlow.value = Resource.Loading()
val expected = listOf(
Resource.Loading(),
Resource.Success(listOf(FavouriteContent(content, true))),
Resource.Loading()
)
sut.get().test {
contentFlow.value = Resource.Success(listOf(content))
contentFlow.value = Resource.Loading()
expected.forEach { expectedItem ->
Assertions.assertEquals(expectedItem, awaitItem())
}
Assertions.assertTrue(cancelAndConsumeRemainingEvents().isEmpty())
}
}
}

View file

@ -0,0 +1,35 @@
package org.fnives.test.showcase.hilt.core.di
import dagger.BindsInstance
import dagger.Component
import org.fnives.test.showcase.hilt.core.login.LogoutUseCaseTest
import org.fnives.test.showcase.hilt.core.session.SessionExpirationListener
import org.fnives.test.showcase.hilt.core.storage.UserDataLocalStorage
import org.fnives.test.showcase.hilt.network.di.BindsBaseOkHttpClient
import org.fnives.test.showcase.hilt.network.di.HiltNetworkModule
import javax.inject.Singleton
@Singleton
@Component(modules = [CoreModule::class, HiltNetworkModule::class, ReloadLoggedInModuleInjectModuleImpl::class, BindsBaseOkHttpClient::class])
internal interface TestCoreComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun setBaseUrl(baseUrl: String): Builder
@BindsInstance
fun setEnableLogging(enableLogging: Boolean): Builder
@BindsInstance
fun setSessionExpirationListener(listener: SessionExpirationListener): Builder
@BindsInstance
fun setUserDataLocalStorage(storage: UserDataLocalStorage): Builder
fun build(): TestCoreComponent
}
fun inject(logoutUseCaseTest: LogoutUseCaseTest)
}

View file

@ -0,0 +1,66 @@
package org.fnives.test.showcase.hilt.core.login
import org.fnives.test.showcase.hilt.core.storage.UserDataLocalStorage
import org.fnives.test.showcase.model.session.Session
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
internal class IsUserLoggedInUseCaseTest {
private lateinit var sut: IsUserLoggedInUseCase
private lateinit var mockUserDataLocalStorage: UserDataLocalStorage
@BeforeEach
fun setUp() {
mockUserDataLocalStorage = mock()
sut = IsUserLoggedInUseCase(mockUserDataLocalStorage)
}
@DisplayName("WHEN nothing is called THEN storage is not called")
@Test
fun creatingDoesntAffectStorage() {
verifyNoInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN session data saved WHEN is user logged in checked THEN true is returned")
@Test
fun sessionInStorageResultsInLoggedIn() {
whenever(mockUserDataLocalStorage.session).doReturn(Session("a", "b"))
val actual = sut.invoke()
Assertions.assertEquals(true, actual)
}
@DisplayName("GIVEN no session data saved WHEN is user logged in checked THEN false is returned")
@Test
fun noSessionInStorageResultsInLoggedOut() {
whenever(mockUserDataLocalStorage.session).doReturn(null)
val actual = sut.invoke()
Assertions.assertEquals(false, actual)
}
@DisplayName("GIVEN no session THEN session THEN no session WHEN is user logged in checked over again THEN every return is correct")
@Test
fun multipleSessionSettingsResultsInCorrectResponses() {
whenever(mockUserDataLocalStorage.session).doReturn(null)
val actual1 = sut.invoke()
whenever(mockUserDataLocalStorage.session).doReturn(Session("", ""))
val actual2 = sut.invoke()
whenever(mockUserDataLocalStorage.session).doReturn(null)
val actual3 = sut.invoke()
Assertions.assertEquals(false, actual1)
Assertions.assertEquals(true, actual2)
Assertions.assertEquals(false, actual3)
}
}

View file

@ -0,0 +1,105 @@
package org.fnives.test.showcase.hilt.core.login
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.shared.UnexpectedException
import org.fnives.test.showcase.hilt.core.storage.UserDataLocalStorage
import org.fnives.test.showcase.hilt.network.auth.LoginRemoteSource
import org.fnives.test.showcase.hilt.network.auth.model.LoginStatusResponses
import org.fnives.test.showcase.model.auth.LoginCredentials
import org.fnives.test.showcase.model.auth.LoginStatus
import org.fnives.test.showcase.model.session.Session
import org.fnives.test.showcase.model.shared.Answer
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class LoginUseCaseTest {
private lateinit var sut: LoginUseCase
private lateinit var mockLoginRemoteSource: LoginRemoteSource
private lateinit var mockUserDataLocalStorage: UserDataLocalStorage
@BeforeEach
fun setUp() {
mockLoginRemoteSource = mock()
mockUserDataLocalStorage = mock()
sut = LoginUseCase(mockLoginRemoteSource, mockUserDataLocalStorage)
}
@DisplayName("GIVEN empty username WHEN trying to login THEN invalid username is returned")
@Test
fun emptyUserNameReturnsLoginStatusError() = runTest {
val expected = Answer.Success(LoginStatus.INVALID_USERNAME)
val actual = sut.invoke(LoginCredentials("", "a"))
Assertions.assertEquals(expected, actual)
verifyNoInteractions(mockLoginRemoteSource)
verifyNoInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN empty password WHEN trying to login THEN invalid password is returned")
@Test
fun emptyPasswordNameReturnsLoginStatusError() = runTest {
val expected = Answer.Success(LoginStatus.INVALID_PASSWORD)
val actual = sut.invoke(LoginCredentials("a", ""))
Assertions.assertEquals(expected, actual)
verifyNoInteractions(mockLoginRemoteSource)
verifyNoInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN invalid credentials response WHEN trying to login THEN invalid credentials is returned ")
@Test
fun invalidLoginResponseReturnInvalidCredentials() = runTest {
val expected = Answer.Success(LoginStatus.INVALID_CREDENTIALS)
whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b")))
.doReturn(LoginStatusResponses.InvalidCredentials)
val actual = sut.invoke(LoginCredentials("a", "b"))
Assertions.assertEquals(expected, actual)
verifyNoInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN success response WHEN trying to login THEN session is saved and success is returned")
@Test
fun validResponseResultsInSavingSessionAndSuccessReturned() = runTest {
val expected = Answer.Success(LoginStatus.SUCCESS)
whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b")))
.doReturn(LoginStatusResponses.Success(Session("c", "d")))
val actual = sut.invoke(LoginCredentials("a", "b"))
Assertions.assertEquals(expected, actual)
verify(mockUserDataLocalStorage, times(1)).session = Session("c", "d")
verifyNoMoreInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN error response WHEN trying to login THEN session is not touched and error is returned")
@Test
fun invalidResponseResultsInErrorReturned() = runTest {
val exception = RuntimeException()
val expected = Answer.Error<LoginStatus>(UnexpectedException(exception))
whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b")))
.doThrow(exception)
val actual = sut.invoke(LoginCredentials("a", "b"))
Assertions.assertEquals(expected, actual)
verifyNoInteractions(mockUserDataLocalStorage)
}
}

View file

@ -0,0 +1,71 @@
package org.fnives.test.showcase.hilt.core.login
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.core.content.ContentRepository
import org.fnives.test.showcase.hilt.core.di.DaggerTestCoreComponent
import org.fnives.test.showcase.hilt.core.di.TestCoreComponent
import org.fnives.test.showcase.hilt.core.storage.UserDataLocalStorage
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.koin.core.context.stopKoin
import org.koin.test.KoinTest
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import javax.inject.Inject
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class LogoutUseCaseTest : KoinTest {
@Inject
lateinit var sut: LogoutUseCase
private lateinit var mockUserDataLocalStorage: UserDataLocalStorage
private lateinit var testCoreComponent: TestCoreComponent
@Inject
lateinit var contentRepository: ContentRepository
@BeforeEach
fun setUp() {
mockUserDataLocalStorage = mock()
testCoreComponent = DaggerTestCoreComponent.builder()
.setBaseUrl("https://a.b.com")
.setEnableLogging(true)
.setSessionExpirationListener(mock())
.setUserDataLocalStorage(mockUserDataLocalStorage)
.build()
testCoreComponent.inject(this)
}
@AfterEach
fun tearDown() {
stopKoin()
}
@DisplayName("WHEN no call THEN storage is not interacted")
@Test
fun initializedDoesntAffectStorage() {
verifyNoInteractions(mockUserDataLocalStorage)
}
@DisplayName("WHEN logout invoked THEN storage is cleared")
@Test
fun logoutResultsInStorageCleaning() = runTest {
val repositoryBefore = contentRepository
sut.invoke()
testCoreComponent.inject(this@LogoutUseCaseTest)
val repositoryAfter = contentRepository
verify(mockUserDataLocalStorage, times(1)).session = null
verifyNoMoreInteractions(mockUserDataLocalStorage)
Assertions.assertNotSame(repositoryBefore, repositoryAfter)
}
}

View file

@ -0,0 +1,38 @@
package org.fnives.test.showcase.hilt.core.session
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
@Suppress("TestFunctionName")
internal class SessionExpirationAdapterTest {
private lateinit var sut: SessionExpirationAdapter
private lateinit var mockSessionExpirationListener: SessionExpirationListener
@BeforeEach
fun setUp() {
mockSessionExpirationListener = mock()
sut = SessionExpirationAdapter(mockSessionExpirationListener)
}
@DisplayName("WHEN nothing is changed THEN delegate is not touched")
@Test
fun verifyNoInteractionsIfNoInvocations() {
verifyNoInteractions(mockSessionExpirationListener)
}
@DisplayName("WHEN onSessionExpired is called THEN delegated is also called")
@Test
fun verifyOnSessionExpirationIsDelegated() {
sut.onSessionExpired()
verify(mockSessionExpirationListener, times(1)).onSessionExpired()
verifyNoMoreInteractions(mockSessionExpirationListener)
}
}

View file

@ -0,0 +1,90 @@
package org.fnives.test.showcase.hilt.core.shared
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.fnives.test.showcase.hilt.network.shared.exceptions.NetworkException
import org.fnives.test.showcase.hilt.network.shared.exceptions.ParsingException
import org.fnives.test.showcase.model.shared.Answer
import org.fnives.test.showcase.model.shared.Resource
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
internal class AnswerUtilsKtTest {
@DisplayName("GIVEN network exception thrown WHEN wrapped into answer THEN answer error is returned")
@Test
fun networkExceptionThrownResultsInError() = runTest {
val exception = NetworkException(Throwable())
val expected = Answer.Error<Unit>(exception)
val actual = wrapIntoAnswer<Unit> { throw exception }
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN parsing exception thrown WHEN wrapped into answer THEN answer error is returned")
@Test
fun parsingExceptionThrownResultsInError() = runTest {
val exception = ParsingException(Throwable())
val expected = Answer.Error<Unit>(exception)
val actual = wrapIntoAnswer<Unit> { throw exception }
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN unexpected throwable thrown WHEN wrapped into answer THEN answer error is returned")
@Test
fun unexpectedExceptionThrownResultsInError() = runTest {
val exception = Throwable()
val expected = Answer.Error<Unit>(UnexpectedException(exception))
val actual = wrapIntoAnswer<Unit> { throw exception }
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN string WHEN wrapped into answer THEN string answer is returned")
@Test
fun stringIsReturnedWrappedIntoSuccess() = runTest {
val expected = Answer.Success("banan")
val actual = wrapIntoAnswer { "banan" }
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN cancellation exception WHEN wrapped into answer THEN cancellation exception is thrown")
@Test
fun cancellationExceptionResultsInThrowingIt() {
Assertions.assertThrows(CancellationException::class.java) {
runBlocking { wrapIntoAnswer { throw CancellationException() } }
}
}
@DisplayName("GIVEN success answer WHEN converted into resource THEN Resource success is returned")
@Test
fun successAnswerConvertsToSuccessResource() {
val expected = Resource.Success("alma")
val actual = Answer.Success("alma").mapIntoResource()
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN error answer WHEN converted into resource THEN Resource error is returned")
@Test
fun errorAnswerConvertsToErrorResource() {
val exception = Throwable()
val expected = Resource.Error<Unit>(exception)
val actual = Answer.Error<Unit>(exception).mapIntoResource()
Assertions.assertEquals(expected, actual)
}
}

View file

@ -0,0 +1,59 @@
package org.fnives.test.showcase.hilt.core.storage
import org.fnives.test.showcase.model.session.Session
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
internal class NetworkSessionLocalStorageAdapterTest {
private lateinit var sut: NetworkSessionLocalStorageAdapter
private lateinit var mockUserDataLocalStorage: UserDataLocalStorage
@BeforeEach
fun setUp() {
mockUserDataLocalStorage = mock()
sut = NetworkSessionLocalStorageAdapter(mockUserDataLocalStorage)
}
@DisplayName("GIVEN null as session WHEN saved THEN its delegated")
@Test
fun settingNullSessionIsDelegated() {
sut.session = null
verify(mockUserDataLocalStorage, times(1)).session = null
verifyNoMoreInteractions(mockUserDataLocalStorage)
}
@DisplayName("GIVEN session WHEN saved THEN its delegated")
@Test
fun settingDataAsSessionIsDelegated() {
val expected = Session("a", "b")
sut.session = Session("a", "b")
verify(mockUserDataLocalStorage, times(1)).session = expected
verifyNoMoreInteractions(mockUserDataLocalStorage)
}
@DisplayName("WHEN session requested THEN its returned from delegated")
@Test
fun gettingSessionReturnsFromDelegate() {
val expected = Session("a", "b")
whenever(mockUserDataLocalStorage.session).doReturn(expected)
val actual = sut.session
Assertions.assertSame(expected, actual)
verify(mockUserDataLocalStorage, times(1)).session
verifyNoMoreInteractions(mockUserDataLocalStorage)
}
}

View file

@ -0,0 +1,24 @@
package org.fnives.test.showcase.hilt.core.testutil
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
class AwaitElementEmitCount(private var counter: Int) {
private val completableDeferred = CompletableDeferred<Unit>()
init {
assert(counter > 0)
}
fun <T> attach(flow: Flow<T>): Flow<T> =
flow.onEach {
counter--
if (counter == 0) {
completableDeferred.complete(Unit)
}
}
suspend fun await() = completableDeferred.await()
}

View file

@ -0,0 +1 @@
mock-maker-inline