Skip to content

Commit

Permalink
Increase code coverage (pt5). (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
Laimiux authored Sep 18, 2024
1 parent 1fda00f commit 94da8d2
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 61 deletions.
16 changes: 2 additions & 14 deletions formula-android-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id("com.android.application")
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
}
Expand All @@ -10,19 +10,6 @@ apply {

android {
namespace = "com.instacart.formula"
defaultConfig {
applicationId = "com.instacart.formula"
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

testOptions {
unitTests {
Expand All @@ -47,4 +34,5 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.truth)
testImplementation(project(":test-utils:android"))
}
21 changes: 0 additions & 21 deletions formula-android-tests/proguard-rules.pro

This file was deleted.

14 changes: 0 additions & 14 deletions formula-android-tests/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,5 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name="com.instacart.formula.NonBoundActivityTest$TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name="com.instacart.formula.NonBoundFragmentActivityTest$TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.instacart.formula

import android.app.Activity
import androidx.fragment.app.FragmentActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.instacart.testutils.android.TestActivity
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand All @@ -16,7 +16,6 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidJUnit4::class)
class NonBoundActivityTest {
class TestActivity : Activity()

private val formulaRule = TestFormulaRule(
initFormula = { app ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
package com.instacart.formula

import androidx.fragment.app.FragmentActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.instacart.testutils.android.TestFragmentActivity
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith

/**
* Tests that formula-android module handles non-bound activities gracefully.
* Tests that formula-android module handles non-bound fragment activities gracefully.
*/
@RunWith(AndroidJUnit4::class)
class NonBoundFragmentActivityTest {
class TestActivity : FragmentActivity()

private val formulaRule = TestFormulaRule(
initFormula = { app ->
FormulaAndroid.init(app) {}
}
)

private val activityRule = ActivityScenarioRule(TestActivity::class.java)
private val activityRule = ActivityScenarioRule(TestFragmentActivity::class.java)

@get:Rule
val rule = RuleChain.outerRule(formulaRule).around(activityRule)
lateinit var scenario: ActivityScenario<TestActivity>
lateinit var scenario: ActivityScenario<TestFragmentActivity>

@Before
fun setup() {
Expand Down
2 changes: 2 additions & 0 deletions formula-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ android {

testOptions {
unitTests.isReturnDefaultValues = true
unitTests.isIncludeAndroidResources = true
}

publishing {
Expand All @@ -40,5 +41,6 @@ dependencies {
testImplementation(libs.mockito.kotlin)
testImplementation(libs.robolectric)
testImplementation(libs.truth)
testImplementation(project(":test-utils:android"))
}

Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class ActivityStoreContextImpl<Activity : FragmentActivity> : ActivityS
select: Activity.() -> Observable<Event>
): Observable<Event> {
// TODO: should probably use startedActivity
return activityAttachEvents()
return attachEventRelay
.switchMap {
val activity = activity
if (activity == null) {
Expand Down Expand Up @@ -105,8 +105,8 @@ internal class ActivityStoreContextImpl<Activity : FragmentActivity> : ActivityS
fun detachActivity(activity: Activity) {
if (this.activity == activity) {
this.activity = null
attachEventRelay.accept(false)
}
attachEventRelay.accept(false)
}

fun updateFragmentLifecycleState(id: FragmentId, newState: Lifecycle.State) {
Expand All @@ -121,8 +121,6 @@ internal class ActivityStoreContextImpl<Activity : FragmentActivity> : ActivityS
fragmentStateUpdated.accept(contract.tag)
}

private fun activityAttachEvents(): Observable<Boolean> = attachEventRelay

private fun fragmentLifecycleState(tag: String): Observable<Lifecycle.State> {
return fragmentStateUpdated
.filter { it == tag }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.instacart.formula.android.internal

import android.os.Looper
import androidx.fragment.app.FragmentActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.instacart.testutils.android.TestFragmentActivity
import com.instacart.testutils.android.activity
import com.instacart.testutils.android.executeOnBackgroundThread
import com.instacart.testutils.android.throwOnTimeout
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch

@RunWith(AndroidJUnit4::class)
class ActivityStoreContextTest {

@Test fun `started activity returns null until onActivityStarted is called`() {
val scenario = ActivityScenario.launch(TestFragmentActivity::class.java)
val storeContext = ActivityStoreContextImpl<TestFragmentActivity>()

// Initially null
assertThat(storeContext.startedActivity()).isNull()

// After attach
storeContext.attachActivity(scenario.activity())
assertThat(storeContext.startedActivity()).isNull()

// After on started
storeContext.onActivityStarted(scenario.activity())
assertThat(storeContext.startedActivity()).isEqualTo(scenario.activity())
}

@Test fun `detaches only if the activity matches`() {
val scenario = ActivityScenario.launch(TestFragmentActivity::class.java)
val storeContext = ActivityStoreContextImpl<TestFragmentActivity>()

val oldActivity = scenario.activity()
val newActivity = scenario.recreate().activity()

storeContext.attachActivity(newActivity)
storeContext.onActivityStarted(newActivity)
storeContext.detachActivity(oldActivity)

assertThat(storeContext.startedActivity()).isEqualTo(newActivity)
}

@Test fun `send posts events on the main thread`() {
val scenario = ActivityScenario.launch(TestFragmentActivity::class.java)
val storeContext = ActivityStoreContextImpl<TestFragmentActivity>()
storeContext.attachActivity(scenario.activity())
storeContext.onActivityStarted(scenario.activity())

val effectThread = mutableListOf<Looper?>()
storeContext.send { effectThread.add(Looper.myLooper()) }
storeContext.sendOnBackgroundThread { effectThread.add(Looper.myLooper()) }

assertThat(effectThread).containsExactly(
Looper.getMainLooper(), Looper.getMainLooper()
)
}

@Test
fun `send drops the action if there is no started activity`() {
val storeContext = ActivityStoreContextImpl<TestFragmentActivity>()

val effectThread = mutableListOf<Looper?>()
storeContext.send { effectThread.add(Looper.myLooper()) }

val result = runCatching {
storeContext.sendOnBackgroundThread { effectThread.add(Looper.myLooper()) }
}
assertThat(effectThread).isEmpty()
assertThat(result.exceptionOrNull()).hasMessageThat().contains(
"timeout"
)
}

private fun <ActivityType : FragmentActivity> ActivityStoreContextImpl<ActivityType>.sendOnBackgroundThread(
action: ActivityType.() -> Unit
) {
val sendLatch = CountDownLatch(1)
executeOnBackgroundThread {
send {
action()
sendLatch.countDown()
}
}
sendLatch.throwOnTimeout()
}
}
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ include(
":samples:stopwatch-compose",
":samples:todoapp"
)
include(
":test-utils:android"
)
1 change: 1 addition & 0 deletions test-utils/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
20 changes: 20 additions & 0 deletions test-utils/android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
}

android {
namespace = "com.instacart.testutils.android"
}

dependencies {
implementation(project(":formula-rxjava3"))
implementation(project(":formula-android"))

implementation(libs.kotlin)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.test.core.ktx)
implementation(libs.lifecycle.extensions)
implementation(libs.robolectric)
}
14 changes: 14 additions & 0 deletions test-utils/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name="com.instacart.testutils.android.TestFragmentActivity"
android:launchMode="standard"
android:exported="false" />

<activity
android:name="com.instacart.testutils.android.TestActivity"
android:launchMode="standard"
android:exported="false" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.instacart.testutils.android

import android.app.Activity

class TestActivity : Activity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.instacart.testutils.android

import android.app.Activity
import android.os.Looper
import androidx.test.core.app.ActivityScenario
import org.robolectric.Shadows
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

fun <A: Activity> ActivityScenario<A>.activity(): A {
return get { this }
}

fun <A: Activity, T> ActivityScenario<A>.get(select: A.() -> T): T {
val list: MutableList<T> = mutableListOf()
onActivity {
list.add(it.select())
}
return list.first()
}

fun CountDownLatch.throwOnTimeout() {
if (!await(100, TimeUnit.MILLISECONDS)) {
throw IllegalStateException("timeout")
}
}

fun executeOnBackgroundThread(action: () -> Unit) {
val initLatch = CountDownLatch(1)
Executors.newSingleThreadExecutor().execute {
action()
initLatch.countDown()
}
initLatch.throwOnTimeout()
Shadows.shadowOf(Looper.getMainLooper()).idle()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.instacart.testutils.android

import androidx.fragment.app.FragmentActivity

class TestFragmentActivity : FragmentActivity()

0 comments on commit 94da8d2

Please sign in to comment.