Skip to content

Commit

Permalink
Add test for FlipperServiceProviderTest (#94)
Browse files Browse the repository at this point in the history
**Background**

Add test for FlipperServiceProviderTest

**Changes**

- Add core:test module
  • Loading branch information
LionZXY authored Nov 2, 2021
1 parent 10c2b2b commit bc2892e
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 26 deletions.
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
// Be careful! See more in /buildSrc/src/main/java/Dependencies.kt#NOTE_CONFIGURATION_PLUGIN
id("org.jetbrains.kotlin.jvm") version "1.5.21"
id("org.jetbrains.kotlin.jvm") version "1.5.31"
}

repositories {
Expand Down
15 changes: 9 additions & 6 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ object Versions {
const val ANDROID_ANNOTATIONS = "1.2.0"
const val ANDROID_MATERIAL = "1.2.0"
const val ANDROID_JETPACK_COMPOSE = "1.0.4"
const val ANDROID_LIFECYCLE = "2.3.1"
const val ANDROID_LIFECYCLE = "2.4.0"

const val FRAGMENT_KTX = "1.3.6"
const val ACTIVITY_KTX = "1.3.1"

// Test
const val ANDROIDX_TEST = "1.2.0"
const val ANDROIDX_TEST_EXT = "1.1.1"
const val ESPRESSO_CORE = "3.2.0"
const val ANDROIDX_TEST_EXT = "1.1.3"
const val JUNIT = "4.12"
const val MOCKITO_KOTLIN = "4.0.0"
const val ROBOELECTRIC = "4.6.1"

const val TIMBER = "4.7.1"
const val TREESSENCE = "1.0.5"
Expand Down Expand Up @@ -128,7 +130,8 @@ object TestingLib {

const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:${Versions.ANDROIDX_TEST}"
const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:${Versions.ANDROIDX_TEST_EXT}"
const val ESPRESSO_CORE = "androidx.test.espresso:espresso-core:${Versions.ESPRESSO_CORE}"
const val ROBOLECTRIC = "org.robolectric:robolectric:4.6.1"
const val ASSERTJ = "org.assertj:assertj-core:3.6.2"
const val MOCKITO = "org.mockito.kotlin:mockito-kotlin:${Versions.MOCKITO_KOTLIN}"
const val ROBOELECTRIC = "org.robolectric:robolectric:${Versions.ROBOELECTRIC}"
const val LIFECYCLE =
"androidx.lifecycle:lifecycle-runtime-testing:${Versions.ANDROID_LIFECYCLE}"
}
4 changes: 0 additions & 4 deletions components/bridge/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,4 @@ dependencies {
kapt(Libs.DAGGER_COMPILER)

implementation(Libs.FASTUTIL)

testImplementation(TestingLib.JUNIT)
androidTestImplementation(TestingLib.ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(TestingLib.ESPRESSO_CORE)
}
13 changes: 13 additions & 0 deletions components/bridge/service/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ plugins {
}
apply<com.flipperdevices.gradle.ConfigurationPlugin>()

android {
testOptions {
unitTests.isIncludeAndroidResources = true
}
}

dependencies {
implementation(project(":components:core:di"))
implementation(project(":components:core:log"))
Expand Down Expand Up @@ -33,4 +39,11 @@ dependencies {

implementation(Libs.DAGGER)
kapt(Libs.DAGGER_COMPILER)

testImplementation(project(":components:core:test"))
testImplementation(TestingLib.JUNIT)
testImplementation(TestingLib.MOCKITO)
testImplementation(TestingLib.ANDROIDX_TEST_EXT_JUNIT)
testImplementation(TestingLib.ROBOELECTRIC)
testImplementation(TestingLib.LIFECYCLE)
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class FlipperService : LifecycleService(), LogTagProvider {
}
}

class FlipperServiceBinder(
class FlipperServiceBinder internal constructor(
val serviceApi: FlipperServiceApi,
compositeListener: CompositeFlipperServiceErrorListener
) : Binder(), CompositeFlipperServiceErrorListener by compositeListener
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,8 @@ class FlipperServiceProviderImpl @Inject constructor(
@Synchronized
private fun stopServiceInternal() {
info { "Internal stop service" }
serviceBinder?.unsubscribe(this)
serviceBinder = null
applicationContext.unbindService(this)
resetInternalWithoutInvalidate()

val stopIntent = Intent(applicationContext, FlipperService::class.java).apply {
action = FlipperService.ACTION_STOP
}
Expand All @@ -148,11 +147,17 @@ class FlipperServiceProviderImpl @Inject constructor(

@Synchronized
private fun resetInternal() {
info { "Reset binder internal, unsubscribe and invalidate" }
info { "Reset binder with invalidate" }
resetInternalWithoutInvalidate()
invalidate()
}

private fun resetInternalWithoutInvalidate() {
info { "Reset binder internal, unsubscribe" }
applicationContext.unbindService(this)
serviceBinder?.unsubscribe(this)
serviceBinder = null
isRequestedForBind = false
invalidate()
}

override fun onError(error: FlipperBleServiceError) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.flipperdevices.bridge.service.impl.utils

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent

/**
* Subscribe on first emitting {@param lifecycleEvent}
Expand All @@ -14,13 +14,10 @@ fun LifecycleOwner.subscribeOnFirst(
) {
lateinit var observer: LifecycleObserver
@Suppress("UnusedPrivateMember")
observer = object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
fun onEvent(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == lifecycleEvent) {
listener()
lifecycle.removeObserver(observer)
}
observer = LifecycleEventObserver { _, event ->
if (event == lifecycleEvent) {
listener()
lifecycle.removeObserver(observer)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.flipperdevices.bridge.service.impl.provider

import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.flipperdevices.bridge.service.api.FlipperServiceApi
import com.flipperdevices.bridge.service.api.provider.FlipperBleServiceConsumer
import com.flipperdevices.bridge.service.impl.FlipperService
import com.flipperdevices.bridge.service.impl.FlipperServiceBinder
import com.flipperdevices.core.test.TimberRule
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.robolectric.annotation.Config

@Config(sdk = [30])
@RunWith(AndroidJUnit4::class)
class FlipperServiceProviderTest {
private val applicationContext = mock<Context>() {
on { packageName } doReturn "com.flipperdevices.bridge.service.impl.provider"
}
private lateinit var subject: FlipperServiceProviderImpl

@Before
fun setUp() {
subject = FlipperServiceProviderImpl(applicationContext)
}

@Test
fun `Start service when exist consumer`() {
val consumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = mock<LifecycleOwner> {
on { lifecycle } doReturn (mock())
}
subject.provideServiceApi(consumer, lifecycleOwner)

verify(applicationContext).startService(
argThat {
component?.className == "com.flipperdevices.bridge.service.impl.FlipperService"
}
)
verify(applicationContext).bindService(any(), any(), any())
}

@Test
fun `Stop service when consumer destroy`() {
val consumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = TestLifecycleOwner()
subject.provideServiceApi(consumer, lifecycleOwner)

verify(applicationContext).bindService(any(), any(), any())

lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)

verify(applicationContext).unbindService(any())
verify(applicationContext).startService(
argThat {
action == FlipperService.ACTION_STOP
}
)
}

@Test
fun `Notify consumers when binder available`() {
val consumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = TestLifecycleOwner()
val serverApi = mock<FlipperServiceApi>()
val binder = FlipperServiceBinder(serverApi, mock())

subject.provideServiceApi(consumer, lifecycleOwner)
subject.onServiceConnected(mock(), binder)

verify(consumer).onServiceApiReady(serverApi)
}

@Test
fun `If we already start notify consumer`() {
val secondConsumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = TestLifecycleOwner()
val serverApi = mock<FlipperServiceApi>()
val binder = FlipperServiceBinder(serverApi, mock())

subject.provideServiceApi(mock(), lifecycleOwner)
subject.onServiceConnected(mock(), binder)
subject.provideServiceApi(secondConsumer, lifecycleOwner)

verify(secondConsumer).onServiceApiReady(serverApi)
}

@Test
fun `Not request start service twice`() {
val lifecycleOwner = TestLifecycleOwner()

subject.provideServiceApi(mock(), lifecycleOwner)
subject.provideServiceApi(mock(), lifecycleOwner)

verify(applicationContext, times(1)).startService(any())
}

@Test
fun `Request start service after stop`() {
val firstConsumer = mock<FlipperBleServiceConsumer>()
val secondConsumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = TestLifecycleOwner()
val firstServerApi = mock<FlipperServiceApi>()
val secondServerApi = mock<FlipperServiceApi>()
assertNotEquals(firstServerApi, secondServerApi)

// Starting
subject.provideServiceApi(firstConsumer, lifecycleOwner)
verify(applicationContext).bindService(any(), any(), any())
subject.onServiceConnected(mock(), FlipperServiceBinder(firstServerApi, mock()))
verify(firstConsumer).onServiceApiReady(firstServerApi)

// Stopping..
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
verify(applicationContext).unbindService(any())
clearInvocations(applicationContext)
// Now service stopped

// Starting...
subject.provideServiceApi(secondConsumer, lifecycleOwner)
verify(applicationContext).bindService(any(), any(), any())
subject.onServiceConnected(mock(), FlipperServiceBinder(secondServerApi, mock()))

verify(secondConsumer).onServiceApiReady(secondServerApi)
}

@Test
fun `Notify about new service api`() {
val consumer = mock<FlipperBleServiceConsumer>()
val lifecycleOwner = TestLifecycleOwner()
val serverApi = mock<FlipperServiceApi>()

// Starting
subject.provideServiceApi(consumer, lifecycleOwner)
verify(applicationContext).bindService(any(), any(), any())
subject.onServiceConnected(mock(), FlipperServiceBinder(serverApi, mock()))
verify(consumer).onServiceApiReady(serverApi)

// Notify about service disconnected
subject.onServiceDisconnected(mock())

verify(applicationContext, times(2)).bindService(any(), any(), any())
}

companion object {
@get:ClassRule
@JvmStatic
var timberRule = TimberRule()
}
}
1 change: 1 addition & 0 deletions components/core/test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
10 changes: 10 additions & 0 deletions components/core/test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("com.android.library")
id("kotlin-android")
}
apply<com.flipperdevices.gradle.ConfigurationPlugin>()

dependencies {
implementation(TestingLib.JUNIT)
implementation(Libs.TIMBER)
}
1 change: 1 addition & 0 deletions components/core/test/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.flipperdevices.core.test" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.flipperdevices.core.test

import org.junit.rules.TestWatcher
import org.junit.runner.Description
import timber.log.Timber

class TimberRule : TestWatcher() {

private val printlnTree = object : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
println("$tag: $message")
}
}

override fun starting(description: Description?) {
super.starting(description)
Timber.plant(printlnTree)
}

override fun finished(description: Description?) {
super.finished(description)
Timber.uproot(printlnTree)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class FileManagerViewModelFactory(
private val path: String
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return FileManagerViewModel(path) as T
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ include(":components:core:log")
include(":components:core:navigation")
include(":components:core:preference")
include(":components:core:ui")
include(":components:core:test")

include(":components:pair:api")
include(":components:pair:impl")
Expand Down

0 comments on commit bc2892e

Please sign in to comment.