Skip to content

Commit

Permalink
Introduce Resolution Strategy pattern implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
AAverin committed Jun 26, 2016
1 parent cb9d2fc commit be96bb3
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import retrofit2.adapter.rxjava.HttpException

interface RxHttpResolution {
fun onHttpException(httpException: HttpException)
fun onGenericRxException(t: Throwable)
}

interface NetworkConnectivityResolution {
fun onConnectivityAvailable()
fun onConnectivityUnavailable()
}

interface LocationRequestResolution {
fun onNetworkLocationError()
}

interface Resolution : RxHttpResolution, NetworkConnectivityResolution, LocationRequestResolution {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import retrofit2.adapter.rxjava.HttpException


abstract class ResolutionByCase : Resolution {

override fun onHttpException(httpException: HttpException) {
val code = httpException.code()
when (code) {
500 -> onInternalServerError()
503 -> onServiceUnavailable()
404 -> onNotFound()
else -> onInternalServerError()
}
}

abstract fun onInternalServerError()

abstract fun onNotFound()

abstract fun onServiceUnavailable()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import pro.averin.anton.clean.android.cookbook.R
import javax.inject.Inject


open class UIResolution @Inject constructor(val uiResolver: UIResolver) : ResolutionByCase() {

override fun onConnectivityAvailable() {
uiResolver.hidePersistentSnackBar()
}

override fun onConnectivityUnavailable() {
uiResolver.showPersistentSnackBar(R.string.error_no_network_connection)
}

override fun onNotFound() {
uiResolver.showSnackBar(R.string.error_not_found)
}

override fun onServiceUnavailable() {
uiResolver.showSnackBar(R.string.error_service_unavailable)
}

override fun onInternalServerError() {
uiResolver.showSnackBar(R.string.error_http_exception)
}

override fun onGenericRxException(t: Throwable) {
t.printStackTrace()
}

override fun onNetworkLocationError() {
uiResolver.showSnackBar(R.string.error_enable_gps)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import android.R
import android.support.design.widget.Snackbar
import android.view.ViewGroup
import pro.averin.anton.clean.android.cookbook.di.ActivityScope
import pro.averin.anton.clean.android.cookbook.ui.common.view.BaseActivity
import javax.inject.Inject


@ActivityScope
class UIResolver @Inject constructor(var baseActivity: BaseActivity) {

private val snackbarRoot: ViewGroup

init {
snackbarRoot = baseActivity.findViewById(R.id.content) as ViewGroup
}

private var persistentSnackbar: Snackbar? = null

fun showSnackBar(messageResource: Int) {
Snackbar.make(snackbarRoot, messageResource, Snackbar.LENGTH_LONG).show()
}

fun showPersistentSnackBar(messageResource: Int) {
persistentSnackbar = Snackbar.make(snackbarRoot, messageResource, Snackbar.LENGTH_INDEFINITE)
persistentSnackbar?.show()
}

fun hidePersistentSnackBar() {
persistentSnackbar?.dismiss()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.Toolbar
import pro.averin.anton.clean.android.cookbook.R
import pro.averin.anton.clean.android.cookbook.di.ActivityScope
import pro.averin.anton.clean.android.cookbook.ui.common.ExtraLifecycleDelegate
import pro.averin.anton.clean.android.cookbook.ui.common.view.BaseActivity
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@

<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>

<string name="error_no_network_connection">Sorry, application does not yet support offline mode. Please, enable your network connection</string>
<string name="error_not_found">Not Found</string>
<string name="error_service_unavailable">Service Unavailable</string>
<string name="error_http_exception">Http Exception</string>
<string name="error_enable_gps">GPS is disabled</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.BDDMockito
import org.mockito.Mock
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import retrofit2.adapter.rxjava.HttpException

@RunWith(PowerMockRunner::class)
@PrepareForTest(HttpException::class)
class ResolutionByCaseTest {

@Mock lateinit var testStub: TestStub
lateinit var classToTest: TestResolutionByCase

@Mock lateinit var httpException: HttpException
@Mock lateinit var genericRxException: Throwable

val internalServerErrorMessage = "InternalServerError"
val notFoundMessage = "NotFound"
val serviceUnavailableMessage = "ServiceUnavailable"

@Before
fun setUp() {
classToTest = TestResolutionByCase(testStub)
}

@Test
fun httpExceptionWith500CallsOnInternalServerError() {
// given
BDDMockito.given(httpException.code()).willReturn(500)

// when
classToTest.onHttpException(httpException)

// then
BDDMockito.verify(testStub).callStub(internalServerErrorMessage)
}

@Test
fun httpExceptionWith503CallsServiceUnavailable() {
// given
BDDMockito.given(httpException.code()).willReturn(503)

// when
classToTest.onHttpException(httpException)

// then
BDDMockito.verify(testStub).callStub(serviceUnavailableMessage)
}

@Test
fun httpExceptionWith404CallsNotFound() {
// given
BDDMockito.given(httpException.code()).willReturn(404)

// when
classToTest.onHttpException(httpException)

// then
BDDMockito.verify(testStub).callStub(notFoundMessage)
}

@Test
fun httpExceptionWithUnknownErrorCallInternalServerError() {
// given
BDDMockito.given(httpException.code()).willReturn(100500)

// when
classToTest.onHttpException(httpException)

// then
BDDMockito.verify(testStub).callStub(internalServerErrorMessage)
}


interface TestStub {
fun callStub(message: String)
}

inner class TestResolutionByCase constructor(val testStub: TestStub) : ResolutionByCase() {
override fun onNetworkLocationError() {
}

override fun onInternalServerError() {
testStub.callStub(internalServerErrorMessage)
}

override fun onNotFound() {
testStub.callStub(notFoundMessage)
}

override fun onServiceUnavailable() {
testStub.callStub(serviceUnavailableMessage)
}

override fun onGenericRxException(t: Throwable) {
}

override fun onConnectivityAvailable() {
}

override fun onConnectivityUnavailable() {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package pro.averin.anton.clean.android.cookbook.ui.common.resolution

import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import pro.averin.anton.clean.android.cookbook.kotlin.test.any
import pro.averin.anton.clean.android.cookbook.kotlin.test.given
import pro.averin.anton.clean.android.cookbook.kotlin.test.mock
import pro.averin.anton.clean.android.cookbook.kotlin.test.verify
import retrofit2.adapter.rxjava.HttpException

@RunWith(PowerMockRunner::class)
@PrepareForTest(
UIResolver::class,
HttpException::class
)
class UIResolutionTest {

@Mock lateinit var uiResolver: UIResolver

@InjectMocks lateinit var classToTest: UIResolution

@Test
fun availableConnectivityHidesPersistentSnackBar() {
// when
classToTest.onConnectivityAvailable()

// then
verify(uiResolver).hidePersistentSnackBar()
}

@Test
fun unavailableConnectivityShowsPersistentSnackBar() {
// when
classToTest.onConnectivityUnavailable()

// then
verify(uiResolver).showPersistentSnackBar(any())
}

@Test
fun internalServerErrorShowsSnackBar() {
// when
classToTest.onInternalServerError()

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun serviceUnavailableShowsSnackbar() {
// when
classToTest.onServiceUnavailable()

// then
verify(uiResolver).showSnackBar(any())
}


@Test
fun notFoundShowsSnackBar() {
// when
classToTest.onNotFound()

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun genericExceptionIsTracked() {
//given
val genericException = mock<Throwable>()

// when
classToTest.onGenericRxException(genericException)

// then
verify(genericException).printStackTrace()
}

@Test
fun networkLocationErrorShowsSnackbar() {
// when
classToTest.onNetworkLocationError()

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun http500ExceptionHandledBySnackbarMessage() {
// given
val httpException = mock<HttpException>()
given(httpException.code()).willReturn(500)

// when
classToTest.onHttpException(httpException)

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun http503ExceptionHandledBySnackbarMessage() {
// given
val httpException = mock<HttpException>()
given(httpException.code()).willReturn(503)

// when
classToTest.onHttpException(httpException)

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun http404ExceptionHandledBySnackbarMessage() {
// given
val httpException = mock<HttpException>()
given(httpException.code()).willReturn(404)

// when
classToTest.onHttpException(httpException)

// then
verify(uiResolver).showSnackBar(any())
}

@Test
fun httpUnknownExceptionHandeledAsInternalServerError() {
// given
val httpException = mock<HttpException>()
given(httpException.code()).willReturn(505)

// when
classToTest.onHttpException(httpException)

// then
verify(uiResolver).showSnackBar(any())
}

}
Loading

0 comments on commit be96bb3

Please sign in to comment.