Skip to content

Commit

Permalink
Add some tests to Account feature
Browse files Browse the repository at this point in the history
  • Loading branch information
herrbert74 committed Dec 29, 2024
1 parent bf2c7ce commit 0b72e7e
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 11 deletions.
1 change: 1 addition & 0 deletions account/account-data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
api(project(":account:account-domain"))
implementation(libs.kotlinx.serialization.json)
testImplementation(libs.squareUp.okhttp3.mockWebServer)
testImplementation(testFixtures(project("::account:account-domain")))

//Remove in AGP 8.9.0 https://issuetracker.google.com/issues/340315591
testFixturesCompileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.zsoltbertalan.flickslate.account.data.network
import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.map
import com.zsoltbertalan.flickslate.account.data.api.AccountDataSource
import com.zsoltbertalan.flickslate.account.data.network.model.toAccount
import com.zsoltbertalan.flickslate.shared.data.util.runCatchingApi
import com.zsoltbertalan.flickslate.shared.model.Account
import com.zsoltbertalan.flickslate.shared.util.Outcome
Expand Down Expand Up @@ -52,13 +53,12 @@ class AccountRemoteDataSource @Inject constructor(
return runCatchingApi {
accountService.getAccountDetails(sessionToken)
}.map { accountDetailsReplyDto ->
val accountName =
if (accountDetailsReplyDto.name.isNullOrEmpty()) accountDetailsReplyDto.username
else accountDetailsReplyDto.name
Account(accountName)
accountDetailsReplyDto.toAccount()
}
}



}

private fun createValidateRequestTokenWithLoginRequestBody(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import retrofit2.Retrofit

@Module
@InstallIn(ActivityRetainedComponent::class)
class MoviesServiceModule {
class AccountServiceModule {

@Provides
@ActivityRetainedScoped
internal fun provideMoviesService(retroFit: Retrofit): AccountService {
internal fun provideAccountService(retroFit: Retrofit): AccountService {
return retroFit.create(AccountService::class.java)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package com.zsoltbertalan.flickslate.account.data.network.model

import com.zsoltbertalan.flickslate.shared.model.Account
import kotlinx.serialization.Serializable

@Suppress("ConstructorParameterNaming")
@Serializable
data class AccountDetailsReplyDto(val name: String?, val username: String)

fun AccountDetailsReplyDto.toAccount(): Account {
val accountName =
if (name.isNullOrEmpty()) username
else name
return Account(accountName)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.zsoltbertalan.flickslate.account.data.network

import com.github.michaelbull.result.Err
import com.zsoltbertalan.flickslate.account.data.network.model.CreateRequestTokenReplyDto
import com.zsoltbertalan.flickslate.account.data.network.model.CreateSessionReplyDto
import com.zsoltbertalan.flickslate.movies.data.network.model.CreateRequestTokenReplyDtoMother
import com.zsoltbertalan.flickslate.movies.data.network.model.CreateSessionReplyDtoMother
import com.zsoltbertalan.flickslate.shared.data.network.model.ErrorBody
import com.zsoltbertalan.flickslate.shared.model.Failure
import io.kotest.matchers.equals.shouldBeEqual
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.Before
import org.junit.Test
import retrofit2.HttpException
import retrofit2.Response
import java.net.HttpURLConnection

class AccountRemoteDataSourceTest {

private val accountService: AccountService = mockk()
private lateinit var accountRemoteDataSource: AccountRemoteDataSource

@Before
fun setup() {
coEvery { accountService.createRequestToken() } returns
CreateRequestTokenReplyDtoMother.createCreateRequestTokenReplyDto()
coEvery { accountService.validateRequestTokenWithLogin(any()) } returns
CreateRequestTokenReplyDtoMother.createCreateRequestTokenReplyDto()
coEvery { accountService.createSession(any()) } returns
CreateSessionReplyDtoMother.createCreateSessionReplyDto()
accountRemoteDataSource = AccountRemoteDataSource(accountService)
}

@Test
fun `when createSessionId called and service returns result then returns correct result`() = runTest {
val result = accountRemoteDataSource.createSessionId("", "")
result.value shouldBeEqual "session123abc"
}

@Test
fun `when createSessionId called and service returns create request failure then returns correct result`() =
runTest {
coEvery { accountService.createRequestToken() } throws createRequestTokenException()
accountRemoteDataSource.createSessionId("", "") shouldBeEqual
Err(Failure.ServerError("Invalid service: this service does not exist."))
}

@Test
fun `when createSessionId called and service returns create 2 failure then returns correct result`() =
runTest {
coEvery { accountService.validateRequestTokenWithLogin(any()) } throws createRequestTokenException()
accountRemoteDataSource.createSessionId("", "") shouldBeEqual
Err(Failure.ServerError("Invalid service: this service does not exist."))
}

@Test
fun `when createSessionId called and service returns create 3 failure then returns correct result`() =
runTest {
coEvery { accountService.createSession(any()) } throws createSessionException()
accountRemoteDataSource.createSessionId("", "") shouldBeEqual
Err(Failure.ServerError("Invalid service: this service does not exist."))
}

@Suppress("unused")
private fun createRequestTokenException(): HttpException {
val errorBody = ErrorBody(
success = false,
status_code = 2,
status_message = "Invalid service: this service does not exist."
)
return HttpException(
Response.error<CreateRequestTokenReplyDto>(
HttpURLConnection.HTTP_NOT_IMPLEMENTED,
Json.encodeToString(errorBody).toResponseBody()
)
)
}

@Suppress("unused")
private fun createSessionException(): HttpException {
val errorBody = ErrorBody(
success = false,
status_code = 2,
status_message = "Invalid service: this service does not exist."
)
return HttpException(
Response.error<CreateSessionReplyDto>(
HttpURLConnection.HTTP_NOT_IMPLEMENTED,
Json.encodeToString(errorBody).toResponseBody()
)
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.zsoltbertalan.flickslate.account.data.network.model

import com.zsoltbertalan.flickslate.movies.data.network.model.AccountReplyDtoMother
import com.zsoltbertalan.flickslate.shared.model.Account
import com.zsoltbertalan.flickslate.shared.model.Movie
import com.zsoltbertalan.flickslate.shared.model.PagingReply
import io.kotest.matchers.shouldBe
import org.junit.Before
import org.junit.Test

class AccountDetailsReplyDtoTest {

@Test
fun `when there is a name in response then name is mapped`() {
val responseDto = AccountReplyDtoMother.createAccountReplyDto()
val mappedResponse = responseDto.toAccount()
mappedResponse.name shouldBe "John Doe"
}

@Test
fun `when there is no name in response then username is mapped`() {
val responseDto = AccountReplyDtoMother.createAccountReplyDtoWithoutName()
val mappedResponse = responseDto.toAccount()
mappedResponse.name shouldBe "Jane Doe"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.zsoltbertalan.flickslate.account.data.repository

import com.github.michaelbull.result.Ok
import com.zsoltbertalan.flickslate.account.data.api.AccountDataSource
import com.zsoltbertalan.flickslate.account.data.network.AccountService
import com.zsoltbertalan.flickslate.movies.domain.model.AccountMother
import com.zsoltbertalan.flickslate.shared.model.Account
import io.kotest.matchers.equals.shouldBeEqual
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test

class AccountAccessorTest {

private val accountLocalDataSource: AccountDataSource.Local = mockk()
private val accountRemoteDataSource: AccountDataSource.Remote = mockk()

private lateinit var accountAccessor: AccountAccessor

@Before
fun setup() {
coEvery { accountLocalDataSource.getAccount() } returns null
coEvery { accountLocalDataSource.getAccessToken() } returns ""
coEvery { accountLocalDataSource.saveAccount(any()) } returns Unit
coEvery { accountLocalDataSource.saveAccessToken(any()) } returns Unit
coEvery { accountRemoteDataSource.createSessionId(any(), any()) } returns Ok(
"session123abc"
)
coEvery { accountRemoteDataSource.getAccountDetails(any()) } returns Ok(
Account("John Doe")
)
coEvery { accountRemoteDataSource.deleteSessionId(any()) } returns Ok(
true
)
accountAccessor = AccountAccessor(
accountRemoteDataSource,
accountLocalDataSource,
)
}

@Test
fun `when login called then returns correct account`() = runTest {
val accountOutcome = accountAccessor.login("john.doe", "password123")
val account = AccountMother.createAccount()
accountOutcome.value shouldBeEqual account
}

@Test
fun `when getAccount called then returns correct account`() = runTest {
val accountOutcome = accountAccessor.getAccount()
val account = AccountMother.createAccount()
if (accountOutcome != null) {
accountOutcome shouldBeEqual account
}
}

@Test
fun `when logout called then session is deleted`() = runTest {
accountAccessor.logout()
coVerify { accountRemoteDataSource.deleteSessionId(any()) }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.zsoltbertalan.flickslate.movies.data.network.model

import com.zsoltbertalan.flickslate.account.data.network.model.AccountDetailsReplyDto

object AccountReplyDtoMother {

fun createAccountReplyDto() = AccountDetailsReplyDto(
name = "John Doe",
username = "Jane Doe"
)

fun createAccountReplyDtoWithoutName() = AccountDetailsReplyDto(
name = null,
username = "Jane Doe"
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.zsoltbertalan.flickslate.movies.data.network.model

import com.zsoltbertalan.flickslate.account.data.network.model.CreateRequestTokenReplyDto

object CreateRequestTokenReplyDtoMother {

fun createCreateRequestTokenReplyDto(success: Boolean = true) = CreateRequestTokenReplyDto(
success = success,
expires_at = "",
request_token = "request123abc"
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zsoltbertalan.flickslate.movies.data.network.model

import com.zsoltbertalan.flickslate.account.data.network.model.CreateSessionReplyDto

object CreateSessionReplyDtoMother {

fun createCreateSessionReplyDto(success: Boolean = true) = CreateSessionReplyDto(
success = success,
session_id = "session123abc"
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zsoltbertalan.flickslate.movies.data.network.model

import com.zsoltbertalan.flickslate.account.data.network.model.DeleteSessionReplyDto

object DeleteSessionReplyDtoMother {

fun createDeleteSessionReplyDto(success: Boolean = true) = DeleteSessionReplyDto(
success = success,
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zsoltbertalan.flickslate.movies.domain.model

import com.zsoltbertalan.flickslate.shared.model.Account

object AccountMother {

fun createAccount() = Account(
name = "John Doe"
)

}
1 change: 0 additions & 1 deletion movies/movies-data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ android {
dependencies {
api(project(":movies:movies-domain"))
testImplementation(libs.squareUp.okhttp3.mockWebServer)
testImplementation(testFixtures(project("::movies:movies-data")))
testImplementation(testFixtures(project("::movies:movies-domain")))

//Remove in AGP 8.9.0 https://issuetracker.google.com/issues/340315591
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import com.zsoltbertalan.flickslate.shared.model.Genre
import com.zsoltbertalan.flickslate.shared.model.Movie
import kotlinx.collections.immutable.toImmutableList

/**
* This is an example of an ObjectMother that can be used in both Unit and Android UI tests.
* As such it would go into its own module in a normal project.
*/
object MovieMother {

fun createMovieList() = listOf(
Expand Down

0 comments on commit 0b72e7e

Please sign in to comment.