Merge pull request #43 from fknives/issue#30-adjust-mockwebserver-readability

Issue#30 adjust mockwebserver readability
This commit is contained in:
Gergely Hegedis 2022-01-24 20:59:52 +02:00 committed by GitHub
commit 2f26467558
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 399 additions and 90 deletions

View file

@ -17,12 +17,7 @@ object SetupAuthenticationState : KoinTest {
mainDispatcherTestRule: MainDispatcherTestRule, mainDispatcherTestRule: MainDispatcherTestRule,
mockServerScenarioSetup: MockServerScenarioSetup mockServerScenarioSetup: MockServerScenarioSetup
) { ) {
mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"))
AuthScenario.Success(
username = "a",
password = "b"
)
)
val activityScenario = ActivityScenario.launch(AuthActivity::class.java) val activityScenario = ActivityScenario.launch(AuthActivity::class.java)
activityScenario.moveToState(Lifecycle.State.RESUMED) activityScenario.moveToState(Lifecycle.State.RESUMED)
val loginRobot = LoginRobot() val loginRobot = LoginRobot()

View file

@ -47,6 +47,8 @@ class MainActivityTest : KoinTest {
@JvmField @JvmField
val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule() val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule()
val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
@Rule @Rule
@JvmField @JvmField
val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule() val mainDispatcherTestRule = SpecificTestConfigurationsFactory.createMainDispatcherTestRule()
@ -60,12 +62,12 @@ class MainActivityTest : KoinTest {
@Before @Before
fun setUp() { fun setUp() {
SpecificTestConfigurationsFactory.createServerTypeConfiguration() SpecificTestConfigurationsFactory.createServerTypeConfiguration()
.invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup) .invoke(mockServerScenarioSetup)
disposable = NetworkSynchronization.registerNetworkingSynchronization() disposable = NetworkSynchronization.registerNetworkingSynchronization()
homeRobot.setupLogin( homeRobot.setupLogin(
mainDispatcherTestRule, mainDispatcherTestRule,
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup
) )
} }
@ -78,8 +80,7 @@ class MainActivityTest : KoinTest {
/** GIVEN initialized MainActivity WHEN signout is clicked THEN user is signed out */ /** GIVEN initialized MainActivity WHEN signout is clicked THEN user is signed out */
@Test @Test
fun signOutClickedResultsInNavigation() { fun signOutClickedResultsInNavigation() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Error(usingRefreshedToken = false))
.setScenario(ContentScenario.Error(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -92,8 +93,7 @@ class MainActivityTest : KoinTest {
/** GIVEN success response WHEN data is returned THEN it is shown on the ui */ /** GIVEN success response WHEN data is returned THEN it is shown on the ui */
@Test @Test
fun successfulDataLoadingShowsTheElementsOnTheUI() { fun successfulDataLoadingShowsTheElementsOnTheUI() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false))
.setScenario(ContentScenario.Success(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -106,8 +106,7 @@ class MainActivityTest : KoinTest {
/** GIVEN success response WHEN item is clicked THEN ui is updated */ /** GIVEN success response WHEN item is clicked THEN ui is updated */
@Test @Test
fun clickingOnListElementUpdatesTheElementsFavouriteState() { fun clickingOnListElementUpdatesTheElementsFavouriteState() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false))
.setScenario(ContentScenario.Success(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -122,8 +121,7 @@ class MainActivityTest : KoinTest {
/** GIVEN success response WHEN item is clicked THEN ui is updated even if activity is recreated */ /** GIVEN success response WHEN item is clicked THEN ui is updated even if activity is recreated */
@Test @Test
fun elementFavouritedIsKeptEvenIfActivityIsRecreated() { fun elementFavouritedIsKeptEvenIfActivityIsRecreated() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false))
.setScenario(ContentScenario.Success(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -143,8 +141,7 @@ class MainActivityTest : KoinTest {
/** GIVEN success response WHEN item is clicked then clicked again THEN ui is updated */ /** GIVEN success response WHEN item is clicked then clicked again THEN ui is updated */
@Test @Test
fun clickingAnElementMultipleTimesProperlyUpdatesIt() { fun clickingAnElementMultipleTimesProperlyUpdatesIt() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false))
.setScenario(ContentScenario.Success(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -161,8 +158,7 @@ class MainActivityTest : KoinTest {
/** GIVEN error response WHEN loaded THEN error is Shown */ /** GIVEN error response WHEN loaded THEN error is Shown */
@Test @Test
fun networkErrorResultsInUIErrorStateShown() { fun networkErrorResultsInUIErrorStateShown() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Error(usingRefreshedToken = false))
.setScenario(ContentScenario.Error(false))
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -174,11 +170,10 @@ class MainActivityTest : KoinTest {
/** GIVEN error response then success WHEN retried THEN success is shown */ /** GIVEN error response then success WHEN retried THEN success is shown */
@Test @Test
fun retryingFromErrorStateAndSucceedingShowsTheData() { fun retryingFromErrorStateAndSucceedingShowsTheData() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(
.setScenario( ContentScenario.Error(usingRefreshedToken = false)
ContentScenario.Error(false) .then(ContentScenario.Success(usingRefreshedToken = false))
.then(ContentScenario.Success(false)) )
)
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -195,11 +190,10 @@ class MainActivityTest : KoinTest {
/** GIVEN success then error WHEN retried THEN error is shown */ /** GIVEN success then error WHEN retried THEN error is shown */
@Test @Test
fun errorIsShownIfTheDataIsFetchedAndErrorIsReceived() { fun errorIsShownIfTheDataIsFetchedAndErrorIsReceived() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(
.setScenario( ContentScenario.Success(usingRefreshedToken = false)
ContentScenario.Success(false) .then(ContentScenario.Error(usingRefreshedToken = false))
.then(ContentScenario.Error(false)) )
)
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
mainDispatcherTestRule.advanceUntilIdleWithIdlingResources() mainDispatcherTestRule.advanceUntilIdleWithIdlingResources()
@ -218,11 +212,10 @@ class MainActivityTest : KoinTest {
/** GIVEN unauthenticated then success WHEN loaded THEN success is shown */ /** GIVEN unauthenticated then success WHEN loaded THEN success is shown */
@Test @Test
fun authenticationIsHandledWithASingleLoading() { fun authenticationIsHandledWithASingleLoading() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(
.setScenario( ContentScenario.Unauthorized(usingRefreshedToken = false)
ContentScenario.Unauthorized(false) .then(ContentScenario.Success(usingRefreshedToken = true))
.then(ContentScenario.Success(true)) )
)
.setScenario(RefreshTokenScenario.Success) .setScenario(RefreshTokenScenario.Success)
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)
@ -237,8 +230,7 @@ class MainActivityTest : KoinTest {
/** GIVEN unauthenticated then error WHEN loaded THEN navigated to auth */ /** GIVEN unauthenticated then error WHEN loaded THEN navigated to auth */
@Test @Test
fun sessionExpirationResultsInNavigation() { fun sessionExpirationResultsInNavigation() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(usingRefreshedToken = false))
.setScenario(ContentScenario.Unauthorized(false))
.setScenario(RefreshTokenScenario.Error) .setScenario(RefreshTokenScenario.Error)
activityScenario = ActivityScenario.launch(MainActivity::class.java) activityScenario = ActivityScenario.launch(MainActivity::class.java)

View file

@ -42,6 +42,7 @@ class AuthActivityTest : KoinTest {
@Rule @Rule
@JvmField @JvmField
val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule() val mockServerScenarioSetupTestRule = MockServerScenarioSetupTestRule()
val mockServerScenarioSetup get() = mockServerScenarioSetupTestRule.mockServerScenarioSetup
@Rule @Rule
@JvmField @JvmField
@ -56,7 +57,7 @@ class AuthActivityTest : KoinTest {
@Before @Before
fun setUp() { fun setUp() {
SpecificTestConfigurationsFactory.createServerTypeConfiguration() SpecificTestConfigurationsFactory.createServerTypeConfiguration()
.invoke(mockServerScenarioSetupTestRule.mockServerScenarioSetup) .invoke(mockServerScenarioSetup)
disposable = NetworkSynchronization.registerNetworkingSynchronization() disposable = NetworkSynchronization.registerNetworkingSynchronization()
} }
@ -69,11 +70,8 @@ class AuthActivityTest : KoinTest {
/** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */ /** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */
@Test @Test
fun properLoginResultsInNavigationToHome() { fun properLoginResultsInNavigationToHome() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(
AuthScenario.Success( AuthScenario.Success(password = "alma", username = "banan")
password = "alma",
username = "banan"
)
) )
activityScenario = ActivityScenario.launch(AuthActivity::class.java) activityScenario = ActivityScenario.launch(AuthActivity::class.java)
loginRobot loginRobot
@ -123,7 +121,7 @@ class AuthActivityTest : KoinTest {
/** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */ /** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */
@Test @Test
fun invalidCredentialsGivenShowsProperErrorMessage() { fun invalidCredentialsGivenShowsProperErrorMessage() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(
AuthScenario.InvalidCredentials(username = "alma", password = "banan") AuthScenario.InvalidCredentials(username = "alma", password = "banan")
) )
activityScenario = ActivityScenario.launch(AuthActivity::class.java) activityScenario = ActivityScenario.launch(AuthActivity::class.java)
@ -144,7 +142,7 @@ class AuthActivityTest : KoinTest {
/** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */ /** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */
@Test @Test
fun networkErrorShowsProperErrorMessage() { fun networkErrorShowsProperErrorMessage() {
mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(
AuthScenario.GenericError(username = "alma", password = "banan") AuthScenario.GenericError(username = "alma", password = "banan")
) )
activityScenario = ActivityScenario.launch(AuthActivity::class.java) activityScenario = ActivityScenario.launch(AuthActivity::class.java)

View file

@ -31,6 +31,13 @@ subprojects { module ->
includeAndroidResources = true includeAndroidResources = true
} }
} }
module.tasks.configureEach { task ->
if (task.taskIdentity.type.toString() == "class org.jetbrains.kotlin.gradle.tasks.KotlinCompile") {
task.kotlinOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
}
}
} }
plugins.withId("com.android.library") { plugins.withId("com.android.library") {
@ -47,5 +54,12 @@ subprojects { module ->
includeAndroidResources = true includeAndroidResources = true
} }
} }
module.tasks.configureEach { task ->
if (task.taskIdentity.type.toString() == "class org.jetbrains.kotlin.gradle.tasks.KotlinCompile") {
task.kotlinOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
}
}
} }
} }

View file

@ -38,6 +38,7 @@ internal class ScenarioToRequestScenario {
is AuthScenario.Success -> CreateAuthSuccessResponse() is AuthScenario.Success -> CreateAuthSuccessResponse()
is AuthScenario.MalformedJsonAsSuccessResponse -> CreateMalformedJsonSuccessResponse() is AuthScenario.MalformedJsonAsSuccessResponse -> CreateMalformedJsonSuccessResponse()
is AuthScenario.UnexpectedJsonAsSuccessResponse -> CreateGenericSuccessResponseByJson("[]") is AuthScenario.UnexpectedJsonAsSuccessResponse -> CreateGenericSuccessResponseByJson("[]")
is AuthScenario.MissingFieldJson -> CreateGenericSuccessResponseByJson("{}")
} }
val requestMatchingChecker = AuthRequestMatchingChecker(authScenario, validateArguments) val requestMatchingChecker = AuthRequestMatchingChecker(authScenario, validateArguments)
return SpecificRequestScenario(requestMatchingChecker, createResponse) return SpecificRequestScenario(requestMatchingChecker, createResponse)

View file

@ -12,4 +12,5 @@ sealed class AuthScenario : GenericScenario<AuthScenario>() {
class GenericError(override val username: String, override val password: String) : AuthScenario() class GenericError(override val username: String, override val password: String) : AuthScenario()
class UnexpectedJsonAsSuccessResponse(override val username: String, override val password: String) : AuthScenario() class UnexpectedJsonAsSuccessResponse(override val username: String, override val password: String) : AuthScenario()
class MalformedJsonAsSuccessResponse(override val username: String, override val password: String) : AuthScenario() class MalformedJsonAsSuccessResponse(override val username: String, override val password: String) : AuthScenario()
class MissingFieldJson(override val username: String, override val password: String) : AuthScenario()
} }

View file

@ -3,5 +3,5 @@ package org.fnives.test.showcase.network.mockserver.scenario.createresponse
import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockResponse
internal class CreateMalformedJsonSuccessResponse : CreateResponse { internal class CreateMalformedJsonSuccessResponse : CreateResponse {
override fun getResponse(): MockResponse = MockResponse().setResponseCode(200).setBody("[") override fun getResponse(): MockResponse = MockResponse().setResponseCode(200).setBody("{")
} }

View file

@ -45,6 +45,11 @@ class CodeKataLoginRemoteSourceTest {
fun invalidJsonMeansParsingException() { fun invalidJsonMeansParsingException() {
} }
@DisplayName("GIVEN json response with missing field WHEN request is fired THEN network exception is thrown")
@Test
fun missingFieldJsonMeansParsingException() {
}
@DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun malformedJsonMeansParsingException() { fun malformedJsonMeansParsingException() {
@ -66,5 +71,13 @@ class CodeKataLoginRemoteSourceTest {
} while (true) } while (true)
} }
} }
internal fun getLoginBodyJson(username: String, password: String): String =
"""
{
"username": "$username",
"password": "$password"
}
""".trimIndent()
} }
} }

View file

@ -30,8 +30,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@RegisterExtension @RegisterExtension
@JvmField @JvmField
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
private val mockServerScenarioSetup private val mockServerScenarioSetup get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
@ -56,7 +55,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@DisplayName("GIVEN successful response WHEN refresh request is fired THEN session is returned") @DisplayName("GIVEN successful response WHEN refresh request is fired THEN session is returned")
@Test @Test
fun successResponseResultsInSession() = runBlocking { fun successResponseResultsInSession() = runBlocking {
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, validateArguments = false)
val expected = ContentData.refreshSuccessResponse val expected = ContentData.refreshSuccessResponse
val actual = sut.refresh(ContentData.refreshSuccessResponse.refreshToken) val actual = sut.refresh(ContentData.refreshSuccessResponse.refreshToken)
@ -67,7 +66,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@DisplayName("GIVEN successful response WHEN refresh request is fired THEN the request is setup properly") @DisplayName("GIVEN successful response WHEN refresh request is fired THEN the request is setup properly")
@Test @Test
fun refreshRequestIsSetupProperly() = runBlocking { fun refreshRequestIsSetupProperly() = runBlocking {
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, validateArguments = false)
sut.refresh(ContentData.refreshSuccessResponse.refreshToken) sut.refresh(ContentData.refreshSuccessResponse.refreshToken)
val request = mockServerScenarioSetup.takeRequest() val request = mockServerScenarioSetup.takeRequest()
@ -82,7 +81,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@DisplayName("GIVEN internal error response WHEN refresh request is fired THEN network exception is thrown") @DisplayName("GIVEN internal error response WHEN refresh request is fired THEN network exception is thrown")
@Test @Test
fun generalErrorResponseResultsInNetworkException() { fun generalErrorResponseResultsInNetworkException() {
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error, validateArguments = false)
Assertions.assertThrows(NetworkException::class.java) { Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.refresh(ContentData.refreshSuccessResponse.refreshToken) } runBlocking { sut.refresh(ContentData.refreshSuccessResponse.refreshToken) }
@ -92,7 +91,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@DisplayName("GIVEN invalid json response WHEN refresh request is fired THEN network exception is thrown") @DisplayName("GIVEN invalid json response WHEN refresh request is fired THEN network exception is thrown")
@Test @Test
fun jsonErrorResponseResultsInParsingException() { fun jsonErrorResponseResultsInParsingException() {
mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) }
@ -102,7 +101,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest {
@DisplayName("GIVEN malformed json response WHEN refresh request is fired THEN parsing exception is thrown") @DisplayName("GIVEN malformed json response WHEN refresh request is fired THEN parsing exception is thrown")
@Test @Test
fun malformedJsonErrorResponseResultsInParsingException() { fun malformedJsonErrorResponseResultsInParsingException() {
mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) } runBlocking { sut.refresh(ContentData.loginSuccessResponse.refreshToken) }

View file

@ -1,6 +1,8 @@
package org.fnives.test.showcase.network.auth package org.fnives.test.showcase.network.auth
import com.squareup.moshi.JsonDataException
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okio.EOFException
import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.auth.LoginCredentials
import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.model.network.BaseUrl
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
@ -25,6 +27,7 @@ import org.koin.test.inject
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONAssert
import org.skyscreamer.jsonassert.JSONCompareMode import org.skyscreamer.jsonassert.JSONCompareMode
import retrofit2.HttpException
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
class LoginRemoteSourceTest : KoinTest { class LoginRemoteSourceTest : KoinTest {
@ -34,8 +37,7 @@ class LoginRemoteSourceTest : KoinTest {
@RegisterExtension @RegisterExtension
@JvmField @JvmField
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
private val mockServerScenarioSetup private val mockServerScenarioSetup get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
@ -60,10 +62,10 @@ class LoginRemoteSourceTest : KoinTest {
@DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned") @DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned")
@Test @Test
fun successResponseIsParsedProperly() = runBlocking { fun successResponseIsParsedProperly() = runBlocking {
mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b")) mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"), validateArguments = false)
val expected = LoginStatusResponses.Success(ContentData.loginSuccessResponse) val expected = LoginStatusResponses.Success(ContentData.loginSuccessResponse)
val actual = sut.login(LoginCredentials("a", "b")) val actual = sut.login(LoginCredentials(username = "a", password = "b"))
Assertions.assertEquals(expected, actual) Assertions.assertEquals(expected, actual)
} }
@ -71,16 +73,16 @@ class LoginRemoteSourceTest : KoinTest {
@DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly") @DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly")
@Test @Test
fun requestProperlySetup() = runBlocking { fun requestProperlySetup() = runBlocking {
mockServerScenarioSetup.setScenario(AuthScenario.Success("a", "b"), false) mockServerScenarioSetup.setScenario(AuthScenario.Success(username = "a", password = "b"), validateArguments = false)
sut.login(LoginCredentials("a", "b")) sut.login(LoginCredentials(username = "a", password = "b"))
val request = mockServerScenarioSetup.takeRequest() val request = mockServerScenarioSetup.takeRequest()
Assertions.assertEquals("POST", request.method) Assertions.assertEquals("POST", request.method)
Assertions.assertEquals("Android", request.getHeader("Platform")) Assertions.assertEquals("Android", request.getHeader("Platform"))
Assertions.assertEquals(null, request.getHeader("Authorization")) Assertions.assertEquals(null, request.getHeader("Authorization"))
Assertions.assertEquals("/login", request.path) Assertions.assertEquals("/login", request.path)
val loginRequest = createExpectedLoginRequestJson("a", "b") val loginRequest = createExpectedLoginRequestJson(username = "a", password = "b")
JSONAssert.assertEquals( JSONAssert.assertEquals(
loginRequest, loginRequest,
request.body.readUtf8(), request.body.readUtf8(),
@ -91,10 +93,10 @@ class LoginRemoteSourceTest : KoinTest {
@DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned") @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned")
@Test @Test
fun badRequestMeansInvalidCredentials() = runBlocking { fun badRequestMeansInvalidCredentials() = runBlocking {
mockServerScenarioSetup.setScenario(AuthScenario.InvalidCredentials("a", "b")) mockServerScenarioSetup.setScenario(AuthScenario.InvalidCredentials(username = "a", password = "b"), validateArguments = false)
val expected = LoginStatusResponses.InvalidCredentials val expected = LoginStatusResponses.InvalidCredentials
val actual = sut.login(LoginCredentials("a", "b")) val actual = sut.login(LoginCredentials(username = "a", password = "b"))
Assertions.assertEquals(expected, actual) Assertions.assertEquals(expected, actual)
} }
@ -102,30 +104,55 @@ class LoginRemoteSourceTest : KoinTest {
@DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun genericErrorMeansNetworkError() { fun genericErrorMeansNetworkError() {
mockServerScenarioSetup.setScenario(AuthScenario.GenericError("a", "b")) mockServerScenarioSetup.setScenario(AuthScenario.GenericError(username = "a", password = "b"), validateArguments = false)
Assertions.assertThrows(NetworkException::class.java) { val actual = Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.login(LoginCredentials("a", "b")) } runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
} }
Assertions.assertEquals("HTTP 500 Server Error", actual.message)
Assertions.assertTrue(actual.cause is HttpException)
} }
@DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun invalidJsonMeansParsingException() { fun invalidJsonMeansParsingException() {
mockServerScenarioSetup.setScenario(AuthScenario.UnexpectedJsonAsSuccessResponse("a", "b")) val response = AuthScenario.UnexpectedJsonAsSuccessResponse(username = "a", password = "b")
mockServerScenarioSetup.setScenario(response, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials("a", "b")) } runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
} }
Assertions.assertEquals("Expected BEGIN_OBJECT but was BEGIN_ARRAY at path \$", actual.message)
Assertions.assertTrue(actual.cause is JsonDataException)
}
@DisplayName("GIVEN json response with missing field WHEN request is fired THEN network exception is thrown")
@Test
fun missingFieldJsonMeansParsingException() {
val response = AuthScenario.MissingFieldJson(username = "a", password = "b")
mockServerScenarioSetup.setScenario(response, validateArguments = false)
val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
}
Assertions.assertEquals("Required value 'accessToken' missing at \$", actual.message)
Assertions.assertTrue(actual.cause is JsonDataException)
} }
@DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun malformedJsonMeansParsingException() { fun malformedJsonMeansParsingException() {
mockServerScenarioSetup.setScenario(AuthScenario.MalformedJsonAsSuccessResponse("a", "b")) val response = AuthScenario.MalformedJsonAsSuccessResponse(username = "a", "b")
mockServerScenarioSetup.setScenario(response, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials("a", "b")) } runBlocking { sut.login(LoginCredentials(username = "a", "b")) }
} }
Assertions.assertEquals("End of input", actual.message)
Assertions.assertTrue(actual.cause is EOFException)
} }
} }

View file

@ -0,0 +1,155 @@
package org.fnives.test.showcase.network.auth
import com.squareup.moshi.JsonDataException
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okio.EOFException
import org.fnives.test.showcase.model.auth.LoginCredentials
import org.fnives.test.showcase.model.network.BaseUrl
import org.fnives.test.showcase.model.session.Session
import org.fnives.test.showcase.network.auth.CodeKataLoginRemoteSourceTest.Companion.getLoginBodyJson
import org.fnives.test.showcase.network.auth.CodeKataLoginRemoteSourceTest.Companion.readResourceFile
import org.fnives.test.showcase.network.auth.model.LoginStatusResponses
import org.fnives.test.showcase.network.di.createNetworkModules
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
import org.fnives.test.showcase.network.shared.exceptions.ParsingException
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.GlobalContext.stopKoin
import org.koin.core.context.startKoin
import org.koin.test.KoinTest
import org.koin.test.inject
import org.mockito.kotlin.mock
import org.skyscreamer.jsonassert.JSONAssert
import org.skyscreamer.jsonassert.JSONCompareMode
import retrofit2.HttpException
class PlainLoginRemoteSourceTest : KoinTest {
private val sut by inject<LoginRemoteSource>()
private lateinit var mockWebServer: MockWebServer
@BeforeEach
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
startKoin {
modules(
createNetworkModules(
baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()),
enableLogging = true,
networkSessionExpirationListenerProvider = { mock() },
networkSessionLocalStorageProvider = { mock() }
).toList()
)
}
}
@AfterEach
fun tearDown() {
stopKoin()
mockWebServer.shutdown()
}
@DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned")
@Test
fun successResponseIsParsedProperly() = runBlocking {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(readResourceFile("success_response_login.json")))
val session = Session(accessToken = "login-access", refreshToken = "login-refresh")
val expected = LoginStatusResponses.Success(session = session)
val actual = sut.login(LoginCredentials(username = "alma", password = "banan"))
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly")
@Test
fun requestProperlySetup() = runBlocking {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(readResourceFile("success_response_login.json")))
sut.login(LoginCredentials(username = "alma", password = "banan"))
val request = mockWebServer.takeRequest()
Assertions.assertEquals("POST", request.method)
Assertions.assertEquals("Android", request.getHeader("Platform"))
Assertions.assertEquals(null, request.getHeader("Authorization"))
Assertions.assertEquals("/mockserver/login", request.path)
val loginRequestBody = getLoginBodyJson(username = "alma", password = "banan")
JSONAssert.assertEquals(
loginRequestBody,
request.body.readUtf8(),
JSONCompareMode.NON_EXTENSIBLE
)
}
@DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned")
@Test
fun badRequestMeansInvalidCredentials() = runBlocking {
mockWebServer.enqueue(MockResponse().setResponseCode(400).setBody("{}"))
val expected = LoginStatusResponses.InvalidCredentials
val actual = sut.login(LoginCredentials(username = "a", password = "b"))
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown")
@Test
fun genericErrorMeansNetworkError() {
mockWebServer.enqueue(MockResponse().setResponseCode(500).setBody("{}"))
val actual = Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
}
Assertions.assertEquals("HTTP 500 Server Error", actual.message)
Assertions.assertTrue(actual.cause is HttpException)
}
@DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown")
@Test
fun invalidJsonMeansParsingException() {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("[]"))
val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
}
Assertions.assertEquals("Expected BEGIN_OBJECT but was BEGIN_ARRAY at path \$", actual.message)
Assertions.assertTrue(actual.cause is JsonDataException)
}
@DisplayName("GIVEN json response with missing field WHEN request is fired THEN network exception is thrown")
@Test
fun missingFieldJsonMeansParsingException() {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("{}"))
val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
}
Assertions.assertEquals("Required value 'accessToken' missing at \$", actual.message)
Assertions.assertTrue(actual.cause is JsonDataException)
}
@DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown")
@Test
fun malformedJsonMeansParsingException() {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("{"))
val actual = Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.login(LoginCredentials(username = "a", password = "b")) }
}
Assertions.assertEquals("End of input", actual.message)
Assertions.assertTrue(actual.cause is EOFException)
}
}

View file

@ -32,8 +32,7 @@ class ContentRemoteSourceImplTest : KoinTest {
@JvmField @JvmField
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
private val mockServerScenarioSetup private val mockServerScenarioSetup get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
@ -59,7 +58,7 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun successResponseParsing() = runBlocking { fun successResponseParsing() = runBlocking {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.Success(false)) mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false), validateArguments = false)
val expected = ContentData.contentSuccess val expected = ContentData.contentSuccess
val actual = sut.get() val actual = sut.get()
@ -71,7 +70,7 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun successResponseRequestIsCorrect() = runBlocking { fun successResponseRequestIsCorrect() = runBlocking {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.Success(false), false) mockServerScenarioSetup.setScenario(ContentScenario.Success(usingRefreshedToken = false), validateArguments = false)
sut.get() sut.get()
val request = mockServerScenarioSetup.takeRequest() val request = mockServerScenarioSetup.takeRequest()
@ -87,7 +86,8 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun dataMissingFieldIsIgnored() = runBlocking { fun dataMissingFieldIsIgnored() = runBlocking {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false)) val response = ContentScenario.SuccessWithMissingFields(usingRefreshedToken = false)
mockServerScenarioSetup.setScenario(response, validateArguments = false)
val expected = ContentData.contentSuccessWithMissingFields val expected = ContentData.contentSuccessWithMissingFields
@ -100,7 +100,7 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun errorResponseResultsInNetworkException() { fun errorResponseResultsInNetworkException() {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.Error(false)) mockServerScenarioSetup.setScenario(ContentScenario.Error(usingRefreshedToken = false), validateArguments = false)
Assertions.assertThrows(NetworkException::class.java) { Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.get() } runBlocking { sut.get() }
@ -111,7 +111,8 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun unexpectedJSONResultsInParsingException() { fun unexpectedJSONResultsInParsingException() {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false)) val response = ContentScenario.UnexpectedJsonAsSuccessResponse(usingRefreshedToken = false)
mockServerScenarioSetup.setScenario(response, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.get() } runBlocking { sut.get() }
@ -122,7 +123,8 @@ class ContentRemoteSourceImplTest : KoinTest {
@Test @Test
fun malformedJSONResultsInParsingException() { fun malformedJSONResultsInParsingException() {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false)) val response = ContentScenario.MalformedJsonAsSuccessResponse(usingRefreshedToken = false)
mockServerScenarioSetup.setScenario(response, validateArguments = false)
Assertions.assertThrows(ParsingException::class.java) { Assertions.assertThrows(ParsingException::class.java) {
runBlocking { sut.get() } runBlocking { sut.get() }

View file

@ -0,0 +1,111 @@
package org.fnives.test.showcase.network.content
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.fnives.test.showcase.model.network.BaseUrl
import org.fnives.test.showcase.model.session.Session
import org.fnives.test.showcase.network.auth.CodeKataLoginRemoteSourceTest.Companion.readResourceFile
import org.fnives.test.showcase.network.di.createNetworkModules
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
import org.fnives.test.showcase.network.shared.exceptions.NetworkException
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.startKoin
import org.koin.core.context.stopKoin
import org.koin.test.KoinTest
import org.koin.test.inject
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
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.verifyZeroInteractions
import org.mockito.kotlin.whenever
import retrofit2.HttpException
class PlainSessionExpirationTest : KoinTest {
private val sut by inject<ContentRemoteSource>()
private lateinit var mockWebServer: MockWebServer
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
private lateinit var mockNetworkSessionExpirationListener: NetworkSessionExpirationListener
@BeforeEach
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
mockNetworkSessionLocalStorage = mock()
mockNetworkSessionExpirationListener = mock()
startKoin {
modules(
createNetworkModules(
baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()),
enableLogging = true,
networkSessionExpirationListenerProvider = { mockNetworkSessionExpirationListener },
networkSessionLocalStorageProvider = { mockNetworkSessionLocalStorage }
).toList()
)
}
}
@AfterEach
fun tearDown() {
stopKoin()
mockWebServer.shutdown()
}
@DisplayName("GIVEN 401 THEN refresh token ok response WHEN content requested THE tokens are refreshed and request retried with new tokens")
@Test
fun successRefreshResultsInRequestRetry() = runBlocking {
mockWebServer.enqueue(MockResponse().setResponseCode(401))
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(readResourceFile("success_response_login.json")))
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("[]"))
var sessionToReturnByMock: Session? = Session(accessToken = "expired-access", refreshToken = "expired-refresh")
whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock }
doAnswer { sessionToReturnByMock = it.arguments[0] as Session? }
.whenever(mockNetworkSessionLocalStorage).session = anyOrNull()
sut.get()
mockWebServer.takeRequest()
val refreshRequest = mockWebServer.takeRequest()
val contentRequestAfterRefreshed = mockWebServer.takeRequest()
Assertions.assertEquals("PUT", refreshRequest.method)
Assertions.assertEquals("/mockserver/login/expired-refresh", refreshRequest.path)
Assertions.assertEquals(null, refreshRequest.getHeader("Authorization"))
Assertions.assertEquals("Android", refreshRequest.getHeader("Platform"))
Assertions.assertEquals("", refreshRequest.body.readUtf8())
Assertions.assertEquals("login-access", contentRequestAfterRefreshed.getHeader("Authorization"))
verifyZeroInteractions(mockNetworkSessionExpirationListener)
}
@DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called")
@Test
fun failingRefreshResultsInSessionExpiration() = runBlocking {
val currentSession = Session(accessToken = "expired-access", refreshToken = "expired-refresh")
whenever(mockNetworkSessionLocalStorage.session).doReturn(currentSession)
mockWebServer.enqueue(MockResponse().setResponseCode(401))
mockWebServer.enqueue(MockResponse().setResponseCode(400))
val actual = Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.get() }
}
Assertions.assertEquals("HTTP 401 Client Error", actual.message)
Assertions.assertTrue(actual.cause is HttpException)
verify(mockNetworkSessionLocalStorage, times(3)).session
verify(mockNetworkSessionLocalStorage, times(1)).session = null
verifyNoMoreInteractions(mockNetworkSessionLocalStorage)
verify(mockNetworkSessionExpirationListener, times(1)).onSessionExpired()
}
}

View file

@ -30,6 +30,7 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.verifyZeroInteractions import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
import retrofit2.HttpException
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
class SessionExpirationTest : KoinTest { class SessionExpirationTest : KoinTest {
@ -39,8 +40,7 @@ class SessionExpirationTest : KoinTest {
@RegisterExtension @RegisterExtension
@JvmField @JvmField
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
private val mockServerScenarioSetup private val mockServerScenarioSetup get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
get() = mockServerScenarioSetupExtensions.mockServerScenarioSetup
private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage private lateinit var mockNetworkSessionLocalStorage: NetworkSessionLocalStorage
private lateinit var mockNetworkSessionExpirationListener: NetworkSessionExpirationListener private lateinit var mockNetworkSessionExpirationListener: NetworkSessionExpirationListener
@ -70,11 +70,11 @@ class SessionExpirationTest : KoinTest {
fun successRefreshResultsInRequestRetry() = runBlocking { fun successRefreshResultsInRequestRetry() = runBlocking {
var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse
mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(
ContentScenario.Unauthorized(false) ContentScenario.Unauthorized(usingRefreshedToken = false)
.then(ContentScenario.Success(true)), .then(ContentScenario.Success(usingRefreshedToken = true)),
false validateArguments = false
) )
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, validateArguments = false)
whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock } whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock }
doAnswer { sessionToReturnByMock = it.arguments[0] as Session? } doAnswer { sessionToReturnByMock = it.arguments[0] as Session? }
.whenever(mockNetworkSessionLocalStorage).session = anyOrNull() .whenever(mockNetworkSessionLocalStorage).session = anyOrNull()
@ -97,8 +97,6 @@ class SessionExpirationTest : KoinTest {
ContentData.refreshSuccessResponse.accessToken, ContentData.refreshSuccessResponse.accessToken,
retryAfterTokenRefreshRequest.getHeader("Authorization") retryAfterTokenRefreshRequest.getHeader("Authorization")
) )
verify(mockNetworkSessionLocalStorage, times(1)).session =
ContentData.refreshSuccessResponse
verifyZeroInteractions(mockNetworkSessionExpirationListener) verifyZeroInteractions(mockNetworkSessionExpirationListener)
} }
@ -106,12 +104,15 @@ class SessionExpirationTest : KoinTest {
@Test @Test
fun failingRefreshResultsInSessionExpiration() = runBlocking { fun failingRefreshResultsInSessionExpiration() = runBlocking {
whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse)
mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(false)) mockServerScenarioSetup.setScenario(ContentScenario.Unauthorized(usingRefreshedToken = false), validateArguments = false)
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error, validateArguments = false)
Assertions.assertThrows(NetworkException::class.java) { val actual = Assertions.assertThrows(NetworkException::class.java) {
runBlocking { sut.get() } runBlocking { sut.get() }
} }
Assertions.assertEquals("HTTP 401 Client Error", actual.message)
Assertions.assertTrue(actual.cause is HttpException)
verify(mockNetworkSessionLocalStorage, times(3)).session verify(mockNetworkSessionLocalStorage, times(3)).session
verify(mockNetworkSessionLocalStorage, times(1)).session = null verify(mockNetworkSessionLocalStorage, times(1)).session = null
verifyNoMoreInteractions(mockNetworkSessionLocalStorage) verifyNoMoreInteractions(mockNetworkSessionLocalStorage)