Skip to content

Commit 576cceb

Browse files
authored
Merge pull request #1700 from OneSignal/user-model/location-unit-tests
[User Model] Location Unit Tests
2 parents 3d2b5b0 + 222cb85 commit 576cceb

File tree

14 files changed

+1138
-18
lines changed

14 files changed

+1138
-18
lines changed

OneSignalSDK/onesignal/location/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,5 @@ dependencies {
8282
testImplementation("io.mockk:mockk:1.13.2")
8383
testImplementation("org.json:json:20180813")
8484
testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
85+
testImplementation("com.google.android.gms:play-services-location:18.0.0")
8586
}

OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/permissions/LocationPermissionController.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ internal interface ILocationPermissionChangedHandler {
4343
}
4444

4545
internal class LocationPermissionController(
46-
private val _application: IApplicationService,
4746
private val _requestPermission: IRequestPermissionService,
4847
private val _applicationService: IApplicationService
4948
) : IRequestPermissionService.PermissionCallback,
@@ -95,7 +94,7 @@ internal class LocationPermissionController(
9594
}
9695

9796
private fun showFallbackAlertDialog(): Boolean {
98-
val activity = _application.current ?: return false
97+
val activity = _applicationService.current ?: return false
9998
AlertDialogPrepromptForAndroidSettings.show(
10099
activity,
101100
activity.getString(R.string.location_permission_name_for_title),

OneSignalSDK/onesignal/location/src/test/java/com/onesignal/location/ExampleUnitTest.kt

Lines changed: 0 additions & 16 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Code taken from https://github.com/kotest/kotest-extensions-robolectric with no changes.
3+
*
4+
* LICENSE: https://github.com/kotest/kotest-extensions-robolectric/blob/master/LICENSE
5+
*/
6+
package com.onesignal.notifications.extensions
7+
8+
import org.junit.runners.model.FrameworkMethod
9+
import org.robolectric.RobolectricTestRunner
10+
import org.robolectric.annotation.Config
11+
import org.robolectric.internal.bytecode.InstrumentationConfiguration
12+
import org.robolectric.pluginapi.config.ConfigurationStrategy
13+
import org.robolectric.plugins.ConfigConfigurer
14+
import java.lang.reflect.Method
15+
16+
internal class ContainedRobolectricRunner(
17+
private val config: Config?
18+
) : RobolectricTestRunner(PlaceholderTest::class.java, injector) {
19+
private val placeHolderMethod: FrameworkMethod = children[0]
20+
val sdkEnvironment = getSandbox(placeHolderMethod).also {
21+
configureSandbox(it, placeHolderMethod)
22+
}
23+
private val bootStrapMethod = sdkEnvironment.bootstrappedClass<Any>(testClass.javaClass)
24+
.getMethod(PlaceholderTest::bootStrapMethod.name)
25+
26+
fun containedBefore() {
27+
Thread.currentThread().contextClassLoader = sdkEnvironment.robolectricClassLoader
28+
super.beforeTest(sdkEnvironment, placeHolderMethod, bootStrapMethod)
29+
}
30+
31+
fun containedAfter() {
32+
super.afterTest(placeHolderMethod, bootStrapMethod)
33+
super.finallyAfterTest(placeHolderMethod)
34+
Thread.currentThread().contextClassLoader = ContainedRobolectricRunner::class.java.classLoader
35+
}
36+
37+
override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
38+
return InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
39+
.doNotAcquirePackage("io.kotest")
40+
.build()
41+
}
42+
43+
override fun getConfig(method: Method?): Config {
44+
val defaultConfiguration = injector.getInstance(ConfigurationStrategy::class.java)
45+
.getConfig(testClass.javaClass, method)
46+
47+
if (config != null) {
48+
val configConfigurer = injector.getInstance(ConfigConfigurer::class.java)
49+
return configConfigurer.merge(defaultConfiguration[Config::class.java], config)
50+
}
51+
52+
return super.getConfig(method)
53+
}
54+
55+
class PlaceholderTest {
56+
@org.junit.Test
57+
fun testPlaceholder() {
58+
}
59+
60+
fun bootStrapMethod() {
61+
}
62+
}
63+
64+
companion object {
65+
private val injector = defaultInjector().build()
66+
}
67+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Code taken from https://github.com/kotest/kotest-extensions-robolectric with a
3+
* fix in the intercept method.
4+
*
5+
* LICENSE: https://github.com/kotest/kotest-extensions-robolectric/blob/master/LICENSE
6+
*/
7+
package com.onesignal.notifications.extensions
8+
9+
import android.app.Application
10+
import io.kotest.core.extensions.ConstructorExtension
11+
import io.kotest.core.extensions.TestCaseExtension
12+
import io.kotest.core.spec.AutoScan
13+
import io.kotest.core.spec.Spec
14+
import io.kotest.core.test.TestCase
15+
import io.kotest.core.test.TestResult
16+
import org.robolectric.annotation.Config
17+
import kotlin.reflect.KClass
18+
import kotlin.reflect.full.findAnnotation
19+
20+
/**
21+
* We override TestCaseExtension to configure the Robolectric environment because TestCase intercept
22+
* occurs on the same thread the test is run. This is unfortunate because it is run for every test,
23+
* rather than every spec. But the SpecExtension intercept is run on a different thread.
24+
*/
25+
@AutoScan
26+
internal class RobolectricExtension : ConstructorExtension, TestCaseExtension {
27+
private fun Class<*>.getParentClass(): List<Class<*>> {
28+
if (superclass == null) return listOf()
29+
return listOf(superclass) + superclass.getParentClass()
30+
}
31+
32+
private fun KClass<*>.getConfig(): Config {
33+
val configAnnotations = listOf(this.java).plus(this.java.getParentClass())
34+
.mapNotNull { it.kotlin.findAnnotation<Config>() }
35+
.asSequence()
36+
37+
val configAnnotation = configAnnotations.firstOrNull()
38+
39+
if (configAnnotation != null) {
40+
return Config.Builder(configAnnotation).build()
41+
}
42+
43+
val robolectricTestAnnotations = listOf(this.java).plus(this.java.getParentClass())
44+
.mapNotNull { it.kotlin.findAnnotation<RobolectricTest>() }
45+
.asSequence()
46+
47+
val application: KClass<out Application>? = robolectricTestAnnotations
48+
.firstOrNull { it.application != KotestDefaultApplication::class }?.application
49+
val sdk: Int? = robolectricTestAnnotations.firstOrNull { it.sdk != -1 }?.takeUnless { it.sdk == -1 }?.sdk
50+
51+
return Config.Builder()
52+
.also { builder ->
53+
if (application != null) {
54+
builder.setApplication(application.java)
55+
}
56+
57+
if (sdk != null) {
58+
builder.setSdk(sdk)
59+
}
60+
}.build()
61+
}
62+
63+
override fun <T : Spec> instantiate(clazz: KClass<T>): Spec? {
64+
clazz.findAnnotation<RobolectricTest>() ?: return null
65+
66+
return ContainedRobolectricRunner(clazz.getConfig())
67+
.sdkEnvironment.bootstrappedClass<Spec>(clazz.java).newInstance()
68+
}
69+
70+
override suspend fun intercept(
71+
testCase: TestCase,
72+
execute: suspend (TestCase) -> TestResult
73+
): TestResult {
74+
// FIXED: Updated code based on https://github.com/kotest/kotest/issues/2717
75+
val hasRobolectricAnnotation = testCase.spec::class.annotations.any { annotation ->
76+
annotation.annotationClass.qualifiedName == RobolectricTest::class.qualifiedName
77+
}
78+
79+
if (!hasRobolectricAnnotation) {
80+
return execute(testCase)
81+
}
82+
83+
val containedRobolectricRunner = ContainedRobolectricRunner(testCase.spec::class.getConfig())
84+
containedRobolectricRunner.containedBefore()
85+
val result = execute(testCase)
86+
containedRobolectricRunner.containedAfter()
87+
return result
88+
}
89+
}
90+
91+
internal class KotestDefaultApplication : Application()
92+
93+
annotation class RobolectricTest(
94+
val application: KClass<out Application> = KotestDefaultApplication::class,
95+
val sdk: Int = -1
96+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.onesignal.location.internal.background
2+
3+
import android.Manifest
4+
import android.app.Application
5+
import androidx.test.core.app.ApplicationProvider
6+
import com.onesignal.debug.LogLevel
7+
import com.onesignal.debug.internal.logging.Logging
8+
import com.onesignal.location.ILocationManager
9+
import com.onesignal.location.internal.capture.ILocationCapturer
10+
import com.onesignal.location.internal.common.LocationConstants
11+
import com.onesignal.location.internal.preferences.ILocationPreferencesService
12+
import com.onesignal.mocks.AndroidMockHelper
13+
import com.onesignal.mocks.MockHelper
14+
import com.onesignal.notifications.extensions.RobolectricTest
15+
import io.kotest.core.spec.style.FunSpec
16+
import io.kotest.matchers.shouldBe
17+
import io.kotest.runner.junit4.KotestTestRunner
18+
import io.mockk.every
19+
import io.mockk.just
20+
import io.mockk.mockk
21+
import io.mockk.runs
22+
import io.mockk.verify
23+
import org.junit.runner.RunWith
24+
import org.robolectric.Shadows
25+
26+
@RobolectricTest
27+
@RunWith(KotestTestRunner::class)
28+
class LocationBackgroundServiceTests : FunSpec({
29+
beforeAny {
30+
Logging.logLevel = LogLevel.NONE
31+
}
32+
33+
test("backgroundRun will capture current location") {
34+
/* Given */
35+
val mockLocationManager = mockk<ILocationManager>()
36+
val mockLocationPreferencesService = mockk<ILocationPreferencesService>()
37+
val mockLocationCapturer = mockk<ILocationCapturer>()
38+
every { mockLocationCapturer.captureLastLocation() } just runs
39+
40+
val locationBackgroundService = LocationBackgroundService(
41+
AndroidMockHelper.applicationService(),
42+
mockLocationManager,
43+
mockLocationPreferencesService,
44+
mockLocationCapturer,
45+
MockHelper.time(1111)
46+
)
47+
48+
/* When */
49+
locationBackgroundService.backgroundRun()
50+
51+
/* Then */
52+
verify(exactly = 1) { mockLocationCapturer.captureLastLocation() }
53+
}
54+
55+
test("scheduleBackgroundRunIn will return null when location services are disabled in SDK") {
56+
/* Given */
57+
val mockLocationManager = mockk<ILocationManager>()
58+
every { mockLocationManager.isLocationShared } returns false
59+
60+
val mockLocationPreferencesService = mockk<ILocationPreferencesService>()
61+
val mockLocationCapturer = mockk<ILocationCapturer>()
62+
63+
val locationBackgroundService = LocationBackgroundService(
64+
AndroidMockHelper.applicationService(),
65+
mockLocationManager,
66+
mockLocationPreferencesService,
67+
mockLocationCapturer,
68+
MockHelper.time(1111)
69+
)
70+
71+
/* When */
72+
val result = locationBackgroundService.scheduleBackgroundRunIn
73+
74+
/* Then */
75+
result shouldBe null
76+
verify(exactly = 1) { mockLocationManager.isLocationShared }
77+
}
78+
79+
test("scheduleBackgroundRunIn will return null when no android permissions") {
80+
/* Given */
81+
val mockLocationManager = mockk<ILocationManager>()
82+
every { mockLocationManager.isLocationShared } returns true
83+
84+
val mockLocationPreferencesService = mockk<ILocationPreferencesService>()
85+
every { mockLocationPreferencesService.lastLocationTime } returns 1111
86+
87+
val mockLocationCapturer = mockk<ILocationCapturer>()
88+
89+
val application: Application = ApplicationProvider.getApplicationContext()
90+
val app = Shadows.shadowOf(application)
91+
app.grantPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
92+
93+
val locationBackgroundService = LocationBackgroundService(
94+
AndroidMockHelper.applicationService(),
95+
mockLocationManager,
96+
mockLocationPreferencesService,
97+
mockLocationCapturer,
98+
MockHelper.time(2222)
99+
)
100+
101+
/* When */
102+
val result = locationBackgroundService.scheduleBackgroundRunIn
103+
104+
/* Then */
105+
result shouldBe (1000 * LocationConstants.TIME_BACKGROUND_SEC) - (2222 - 1111)
106+
}
107+
})

0 commit comments

Comments
 (0)