Skip to content

feat(Android): #8011 Convert disabled Android Unit Tests to Instrumentation Tests #8041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions lib/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ android {
defaultConfig {
minSdkVersion safeExtGetFallbackLowerBound('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
targetSdkVersion safeExtGetFallbackLowerBound('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
Expand All @@ -53,6 +55,15 @@ android {
}

testOptions {
managedDevices {
localDevices {
pixel3aapi34 {
device = "Pixel 3a"
apiLevel = 34
systemImageSource = "aosp-atd"
}
}
}
unitTests.includeAndroidResources = true
unitTests.all { t ->
maxHeapSize = "4g"
Expand Down Expand Up @@ -102,6 +113,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+'


if ("Playground".toLowerCase() == rootProject.name.toLowerCase()) {
// tests only for our playground
testImplementation 'junit:junit:4.13.2'
Expand All @@ -112,5 +124,22 @@ dependencies {
testImplementation 'org.mockito:mockito-inline:4.6.1'
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"

// Core testing libraries
androidTestImplementation "androidx.test.ext:junit:1.2.1"
androidTestImplementation 'org.assertj:assertj-core:3.11.1'

androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1"

androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.4"
androidTestImplementation 'org.opentest4j:opentest4j:1.2.0'
androidTestImplementation ("org.mockito.kotlin:mockito-kotlin:5.4.0") {
exclude group: 'org.mockito', module: 'mockito-core'
}

androidTestImplementation "androidx.test.uiautomator:uiautomator:2.3.0"

androidTestImplementation("com.facebook.react:hermes-android")

}
}
17 changes: 17 additions & 0 deletions lib/android/app/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativenavigation.test">

<application
android:name="com.reactnativenavigation.TestApplication">
<activity
android:name="com.reactnativenavigation.TestActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
>
<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
@@ -0,0 +1,4 @@
package com.reactnativenavigation

abstract class BaseAndroidTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.reactnativenavigation

class TestActivity : NavigationActivity() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.reactnativenavigation

import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.shell.MainReactPackage
import com.reactnativenavigation.react.NavigationPackage
import com.reactnativenavigation.react.NavigationReactNativeHost

class TestApplication : NavigationApplication() {


override val reactNativeHost: ReactNativeHost
get() = object : NavigationReactNativeHost(this) {
override fun getJSMainModuleName(): String {
return "index"
}

override fun getUseDeveloperSupport(): Boolean {
return false
}

public override fun getPackages(): List<ReactPackage> {
val packages = listOf(MainReactPackage(null), NavigationPackage())
return packages
}

override val isHermesEnabled: Boolean
get() = true

override val isNewArchEnabled: Boolean
get() = true
}

override fun onCreate() {
super.onCreate()
load()
}

override val reactHost: ReactHost
get() = getDefaultReactHost(this, reactNativeHost)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.reactnativenavigation;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;

import com.reactnativenavigation.mocks.TitleBarReactViewCreatorMock;
import com.reactnativenavigation.mocks.TopBarBackgroundViewCreatorMock;
import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock;
import com.reactnativenavigation.mocks.TypefaceLoaderMock;
import com.reactnativenavigation.options.Options;
import com.reactnativenavigation.options.params.Bool;
import com.reactnativenavigation.utils.RenderChecker;
import com.reactnativenavigation.viewcontrollers.stack.StackPresenter;
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.utils.CompatUtils;
import com.reactnativenavigation.utils.ImageLoader;
import com.reactnativenavigation.utils.UiUtils;
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
import com.reactnativenavigation.viewcontrollers.stack.topbar.button.IconResolver;
import com.reactnativenavigation.viewcontrollers.stack.StackControllerBuilder;
import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController;
import com.reactnativenavigation.views.stack.StackLayout;
import com.reactnativenavigation.views.stack.topbar.TopBar;

import org.mockito.Mockito;

public class TestUtils {
public static StackControllerBuilder newStackController(Activity activity) {
TopBarController topBarController = new TopBarController() {
@Override
protected TopBar createTopBar(@NonNull Context context, @NonNull StackLayout stackLayout) {
TopBar topBar = super.createTopBar(context, stackLayout);
topBar.layout(0, 0, 1000, UiUtils.getTopBarHeight(context));
return topBar;
}
};
return new StackControllerBuilder(activity, Mockito.mock(EventEmitter.class))
.setId("stack" + CompatUtils.generateViewId())
.setChildRegistry(new ChildControllersRegistry())
.setTopBarController(topBarController)
.setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(),
new TopBarBackgroundViewCreatorMock(), new TitleBarButtonCreatorMock(),
new IconResolver(activity, new ImageLoader()), new TypefaceLoaderMock(), new RenderChecker(),
new Options()))
.setInitialOptions(new Options());
}

public static void hideBackButton(ViewController<?> viewController) {
viewController.options.topBar.buttons.back.visible = new Bool(false);
}

public static <T extends View> T spyOn(T child) {
ViewGroup parent = (ViewGroup) child.getParent();
int indexOf = parent.indexOfChild(child);
parent.removeView(child);
T spy = Mockito.spy(child);
parent.addView(spy, indexOf);
return spy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.reactnativenavigation.mocks;

import static com.reactnativenavigation.utils.ObjectUtils.perform;

import android.app.Activity;
import android.content.Context;
import android.view.MotionEvent;

import androidx.annotation.NonNull;

import com.reactnativenavigation.options.Options;
import com.reactnativenavigation.react.ReactView;
import com.reactnativenavigation.viewcontrollers.child.ChildController;
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.component.ComponentPresenterBase;
import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener;
import com.reactnativenavigation.views.component.ReactComponent;

public class SimpleViewController extends ChildController<SimpleViewController.SimpleView> {
private final ComponentPresenterBase presenter = new ComponentPresenterBase();

public SimpleViewController(Activity activity, ChildControllersRegistry childRegistry, String id, Options options) {
this(activity, childRegistry, id, new Presenter(activity, new Options()), options);
}

public SimpleViewController(Activity activity, ChildControllersRegistry childRegistry, String id, Presenter presenter, Options options) {
super(activity, childRegistry, id, presenter, options);
}

@Override
public SimpleView createView() {
return new SimpleView(getActivity());
}

@Override
public void sendOnNavigationButtonPressed(String buttonId) {
getView().sendOnNavigationButtonPressed(buttonId);
}

@Override
public void destroy() {
if (!isDestroyed()) performOnParentController(parent -> parent.onChildDestroyed(this));
super.destroy();
}

@NonNull
@Override
public String toString() {
return "SimpleViewController " + getId();
}

@Override
public int getTopInset() {
int statusBarInset = resolveCurrentOptions().statusBar.isHiddenOrDrawBehind() ? 0 : 63;
return statusBarInset + perform(getParentController(), 0, p -> p.getTopInset(this));
}

@Override
public void applyBottomInset() {
if (view != null) presenter.applyBottomInset(view, getBottomInset());
}

@Override
public String getCurrentComponentName() {
return null;
}

public static class SimpleView extends ReactView implements ReactComponent {

public SimpleView(@NonNull Context context) {
super(context, "compId", "compName");
}

@Override
public boolean isRendered() {
return getChildCount() >= 1;
}

@Override
public boolean isReady() {
return false;
}

@Override
public ReactView asView() {
return this;
}

@Override
public void destroy() {

}

@Override
public void sendOnNavigationButtonPressed(String buttonId) {

}

@Override
public ScrollEventListener getScrollEventListener() {
return null;
}

@Override
public void dispatchTouchEventToJs(MotionEvent event) {

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.reactnativenavigation.mocks;

import android.app.Activity;

import com.reactnativenavigation.options.ComponentOptions;
import com.reactnativenavigation.react.events.ComponentType;
import com.reactnativenavigation.views.stack.topbar.titlebar.TitleBarButtonCreator;
import com.reactnativenavigation.views.stack.topbar.titlebar.TitleBarReactButtonView;

public class TitleBarButtonCreatorMock extends TitleBarButtonCreator {
public TitleBarButtonCreatorMock() {
}

@Override
public TitleBarReactButtonView create(Activity activity, ComponentOptions component) {
return new TitleBarReactButtonView(activity, component) {
@Override
public void sendComponentStart(ComponentType type) {
}

@Override
public void sendComponentStop(ComponentType type) {
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.reactnativenavigation.mocks;

import com.reactnativenavigation.views.stack.topbar.titlebar.TitleBarReactViewCreator;

public class TitleBarReactViewCreatorMock extends TitleBarReactViewCreator {
public TitleBarReactViewCreatorMock() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.reactnativenavigation.mocks;

import com.reactnativenavigation.views.stack.topbar.TopBarBackgroundViewCreator;

public class TopBarBackgroundViewCreatorMock extends TopBarBackgroundViewCreator {
public TopBarBackgroundViewCreatorMock() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.reactnativenavigation.mocks;

import android.content.Context;
import android.graphics.Typeface;
import com.reactnativenavigation.options.parsers.TypefaceLoader;
import org.mockito.Mockito;

import java.util.Map;

public class TypefaceLoaderMock extends TypefaceLoader {
private Map<String, Typeface> mockTypefaces;

public TypefaceLoaderMock() {
super(Mockito.mock(Context.class));
}

public TypefaceLoaderMock(Map<String, Typeface> mockTypefaces) {
this();
this.mockTypefaces = mockTypefaces;
}

@Override
public Typeface getDefaultTypeFace() {
return Typeface.DEFAULT;
}

@Override
public Typeface getTypeFace(String fontFamilyName, String fontStyle, String fontWeight, Typeface defaultTypeFace) {
if (mockTypefaces != null && mockTypefaces.containsKey(fontFamilyName)) {
return mockTypefaces.get(fontFamilyName);
}
return defaultTypeFace;
}
}
Loading