From e993e3775fc62f2748b5ed80fba653d4fda3dc21 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 10 Mar 2025 03:32:07 -0700 Subject: [PATCH] Fix disabled ReactHost tests (#49907) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49907 These tests have been disabled for a while due to feature flags being introduced, fusebox flags with native dependencies, and the removal of Powermock. Changelog: [Internal] Reviewed By: Abbondanzo Differential Revision: D70803553 --- .../facebook/react/runtime/ReactHostImpl.java | 8 +- .../facebook/react/runtime/ReactHostTest.kt | 168 +++++++----------- 2 files changed, 73 insertions(+), 103 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java index 2c3e033e27fe..b7680f25c884 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java @@ -20,6 +20,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Nullsafe; @@ -1773,7 +1774,9 @@ private Task getOrCreateDestroyTask(final String reason, @Nullable Excepti return mDestroyTask; } - private @Nullable ReactHostInspectorTarget getOrCreateReactHostInspectorTarget() { + @VisibleForTesting + /* package */ @Nullable + ReactHostInspectorTarget getOrCreateReactHostInspectorTarget() { if (mReactHostInspectorTarget == null && InspectorFlags.getFuseboxEnabled()) { // NOTE: ReactHostInspectorTarget only retains a weak reference to `this`. mReactHostInspectorTarget = new ReactHostInspectorTarget(this); @@ -1783,7 +1786,8 @@ private Task getOrCreateDestroyTask(final String reason, @Nullable Excepti } @ThreadConfined(UI) - private void unregisterInstanceFromInspector(final @Nullable ReactInstance reactInstance) { + @VisibleForTesting + /* package */ void unregisterInstanceFromInspector(final @Nullable ReactInstance reactInstance) { if (reactInstance != null) { if (InspectorFlags.getFuseboxEnabled()) { Assertions.assertCondition( diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt index 4bd23cdd54e3..52b7ff4bb23d 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt @@ -8,113 +8,103 @@ package com.facebook.react.runtime import android.app.Activity -import android.os.Looper import com.facebook.react.MemoryPressureRouter -import com.facebook.react.bridge.JSBundleLoader -import com.facebook.react.bridge.MemoryPressureListener import com.facebook.react.bridge.UIManager import com.facebook.react.common.LifecycleState import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.devsupport.ReleaseDevSupportManager -import com.facebook.react.fabric.ComponentFactory -import com.facebook.react.interfaces.TaskInterface -import com.facebook.react.runtime.internal.bolts.TaskCompletionSource +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlagsDefaults +import com.facebook.react.runtime.internal.bolts.Task import com.facebook.react.uimanager.events.BlackHoleEventDispatcher import com.facebook.testutils.shadows.ShadowSoLoader -import java.util.concurrent.TimeUnit -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any import org.mockito.MockedConstruction -import org.mockito.Mockito -import org.mockito.Mockito.withSettings +import org.mockito.Mockito.mockConstruction +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows import org.robolectric.android.controller.ActivityController import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode /** Tests [ReactHostImpl] */ @RunWith(RobolectricTestRunner::class) @Config(shadows = [ShadowSoLoader::class]) -@LooperMode(LooperMode.Mode.PAUSED) @OptIn(UnstableReactNativeAPI::class) class ReactHostTest { private lateinit var reactHostDelegate: ReactHostDelegate - private lateinit var reactInstance: ReactInstance - private lateinit var memoryPressureRouter: MemoryPressureRouter - private lateinit var jSBundleLoader: JSBundleLoader private lateinit var reactHost: ReactHostImpl private lateinit var activityController: ActivityController - private lateinit var componentFactory: ComponentFactory - private lateinit var bridgelessReactContext: BridgelessReactContext private lateinit var mockedReactInstanceCtor: MockedConstruction - private lateinit var mockedReactHostInspectorTargetCtor: - MockedConstruction private lateinit var mockedBridgelessReactContextCtor: MockedConstruction private lateinit var mockedMemoryPressureRouterCtor: MockedConstruction - private lateinit var mockedTaskCompletionSourceCtor: MockedConstruction> @Before fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + ReactNativeFeatureFlags.override(ReactNativeNewArchitectureFeatureFlagsDefaults()) + activityController = Robolectric.buildActivity(Activity::class.java).create().start().resume() - reactHostDelegate = Mockito.mock(ReactHostDelegate::class.java) - reactInstance = Mockito.mock(ReactInstance::class.java) - memoryPressureRouter = Mockito.mock(MemoryPressureRouter::class.java) - jSBundleLoader = Mockito.mock(JSBundleLoader::class.java) - componentFactory = Mockito.mock(ComponentFactory::class.java) - bridgelessReactContext = Mockito.mock(BridgelessReactContext::class.java) + mockedReactInstanceCtor = mockConstruction(ReactInstance::class.java) + mockedBridgelessReactContextCtor = mockConstruction(BridgelessReactContext::class.java) + mockedMemoryPressureRouterCtor = mockConstruction(MemoryPressureRouter::class.java) - mockedReactInstanceCtor = Mockito.mockConstruction(ReactInstance::class.java) - mockedReactHostInspectorTargetCtor = - Mockito.mockConstruction(ReactHostInspectorTarget::class.java) - mockedBridgelessReactContextCtor = Mockito.mockConstruction(BridgelessReactContext::class.java) - mockedMemoryPressureRouterCtor = Mockito.mockConstruction(MemoryPressureRouter::class.java) + reactHostDelegate = mock() + whenever(reactHostDelegate.jsBundleLoader).thenReturn(mock()) - Mockito.doReturn(jSBundleLoader).`when`(reactHostDelegate).jsBundleLoader reactHost = - ReactHostImpl( - activityController.get().application, reactHostDelegate, componentFactory, false, false) - val taskCompletionSource = TaskCompletionSource().apply { setResult(true) } - mockedTaskCompletionSourceCtor = - Mockito.mockConstruction( - TaskCompletionSource::class.java, withSettings().useConstructor(taskCompletionSource)) + spy( + ReactHostImpl( + activityController.get().application, + reactHostDelegate, + mock(), + Task.IMMEDIATE_EXECUTOR, + Task.IMMEDIATE_EXECUTOR, + false /* allowPackagerServerAccess */, + false /* useDevSupport */, + )) + doReturn(null).whenever(reactHost).getOrCreateReactHostInspectorTarget() + doNothing().whenever(reactHost).unregisterInstanceFromInspector(any()) } @After fun tearDown() { mockedReactInstanceCtor.close() - mockedReactHostInspectorTargetCtor.close() mockedBridgelessReactContextCtor.close() mockedMemoryPressureRouterCtor.close() - mockedTaskCompletionSourceCtor.close() + ReactNativeFeatureFlags.dangerouslyReset() } @Test fun getEventDispatcher_returnsBlackHoleEventDispatcher() { val eventDispatcher = reactHost.eventDispatcher - Assertions.assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher::class.java) + assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher::class.java) } @Test fun getUIManager_returnsNullIfNoInstance() { val uiManager: UIManager? = reactHost.uiManager - Assertions.assertThat(uiManager).isNull() + assertThat(uiManager).isNull() } @Test fun testGetDevSupportManager_withRelease() { // BridgelessDevSupportManager is created only for debug // we check if it was instantiated or if ReleaseDevSupportManager was created (for release). - Assertions.assertThat(reactHost.devSupportManager) - .isInstanceOf(ReleaseDevSupportManager::class.java) + assertThat(reactHost.devSupportManager).isInstanceOf(ReleaseDevSupportManager::class.java) } @Test @@ -123,80 +113,56 @@ class ReactHostTest { ReactHostImpl( activityController.get().application, reactHostDelegate, - componentFactory, - false, + mock(), + Task.IMMEDIATE_EXECUTOR, + Task.IMMEDIATE_EXECUTOR, + false /* allowPackagerServerAccess */, true /* useDevSupport */) - Assertions.assertThat(reactHost.devSupportManager) - .isNotInstanceOf(ReleaseDevSupportManager::class.java) + assertThat(reactHost.devSupportManager).isNotInstanceOf(ReleaseDevSupportManager::class.java) } @Test - @Ignore("Test is currently failing in OSS and needs to be looked into") fun testStart() { - Assertions.assertThat(reactHost.isInstanceInitialized).isFalse() - waitForTaskUIThread(reactHost.start()) - Assertions.assertThat(reactHost.isInstanceInitialized).isTrue() - Assertions.assertThat(reactHost.currentReactContext).isNotNull() - Mockito.verify(memoryPressureRouter) - .addMemoryPressureListener(ArgumentMatchers.any() as MemoryPressureListener) + assertThat(reactHost.isInstanceInitialized).isFalse() + reactHost.start() + assertThat(reactHost.isInstanceInitialized).isTrue() + assertThat(reactHost.currentReactContext).isNotNull() + + val memoryPressureRouter = mockedMemoryPressureRouterCtor.constructed().first() + verify(memoryPressureRouter).addMemoryPressureListener(any()) } @Test - @Ignore("Test is currently failing in OSS and needs to be looked into") fun testDestroy() { - startReactHost() - waitForTaskUIThread(reactHost.destroy("Destroying from testing infra", null)) - Assertions.assertThat(reactHost.isInstanceInitialized).isFalse() - Assertions.assertThat(reactHost.currentReactContext).isNull() + reactHost.start() + reactHost.destroy("Destroying from testing infra", null) + assertThat(reactHost.isInstanceInitialized).isFalse() + assertThat(reactHost.currentReactContext).isNull() } @Test - @Ignore("Test is currently failing in OSS and needs to be looked into") fun testReload() { - startReactHost() + reactHost.start() val oldReactContext = reactHost.currentReactContext - val newReactContext = Mockito.mock(BridgelessReactContext::class.java) - Assertions.assertThat(newReactContext).isNotEqualTo(oldReactContext) - // TODO This should be replaced with proper mocking once this test is un-ignored - // whenNew(BridgelessReactContext.class).withAnyArguments().thenReturn(newReactContext); - waitForTaskUIThread(reactHost.reload("Reload from testing infra")) - Assertions.assertThat(reactHost.isInstanceInitialized).isTrue() - Assertions.assertThat(reactHost.currentReactContext).isNotNull() - Assertions.assertThat(reactHost.currentReactContext).isEqualTo(newReactContext) - Assertions.assertThat(reactHost.currentReactContext).isNotEqualTo(oldReactContext) + reactHost.reload("Reload from testing infra") + assertThat(mockedBridgelessReactContextCtor.constructed().count()).isEqualTo(2) + assertThat(mockedBridgelessReactContextCtor.constructed().first()).isEqualTo(oldReactContext) + val newReactContext = mockedBridgelessReactContextCtor.constructed().last() + assertThat(reactHost.isInstanceInitialized).isTrue() + assertThat(reactHost.currentReactContext).isNotNull() + assertThat(reactHost.currentReactContext).isEqualTo(newReactContext) + assertThat(reactHost.currentReactContext).isNotEqualTo(oldReactContext) } @Test - @Ignore("Test is currently failing in OSS and needs to be looked into") fun testLifecycleStateChanges() { - startReactHost() - Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) + reactHost.start() + assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) reactHost.onHostResume(activityController.get()) - Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.RESUMED) + assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.RESUMED) reactHost.onHostPause(activityController.get()) - Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_RESUME) + assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_RESUME) reactHost.onHostDestroy(activityController.get()) - Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) - } - - private fun startReactHost() { - waitForTaskUIThread(reactHost.start()) - } - - companion object { - @Throws(InterruptedException::class) - private fun waitForTaskUIThread(task: TaskInterface) { - var isTaskCompleted = false - while (!isTaskCompleted) { - if (!task.waitForCompletion(4, TimeUnit.MILLISECONDS)) { - Shadows.shadowOf(Looper.getMainLooper()).idle() - } else { - if (task.isCancelled() || task.isFaulted()) { - throw RuntimeException("Task was cancelled or faulted. Error: " + task.getError()) - } - isTaskCompleted = true - } - } - } + assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) } }