package org.edx.mobile.view;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.rule.UiThreadTestRule;
import android.support.test.runner.AndroidJUnit4;
import com.facebook.testing.screenshot.Screenshot;
import org.edx.mobile.loader.AsyncTaskResult;
import org.edx.mobile.test.EdxInstrumentationTestApplication;
import org.edx.mobile.test.GenericSuperclassUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static org.mockito.Mockito.mock;
/**
* Extend this class to create an instrumentation test that automatically:
* Starts the activity.
* Injects a mock presenter into the activity.
* Makes the MVP view interface accessible as a variable named `view`.
* Executes `view` interface method calls on the application's UI thread.
* Snaps a screenshot of your activity at the end of each test.
*/
@RunWith(AndroidJUnit4.class)
public abstract class PresenterActivityScreenshotTest<ActivityT extends PresenterActivity<PresenterT, ViewT>, PresenterT extends Presenter<ViewT>, ViewT> {
protected ActivityT activity;
protected ViewT view;
protected PresenterT presenter;
@Rule
public ActivityTestRule<ActivityT> mActivityRule = new ActivityTestRule<>(getActivityType(), true, false);
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Rule
public TestName testName = new TestName();
@Before
public void before() {
this.presenter = mock(getPresenterType());
((EdxInstrumentationTestApplication) InstrumentationRegistry.getTargetContext().getApplicationContext()).setNextPresenter(presenter);
this.activity = mActivityRule.launchActivity(null);
// To simplify tests, we automatically execute view methods on the application's UI thread.
this.view = UiThreadInvocationHandler.newProxyInstance(uiThreadTestRule, activity.view, getViewType());
}
@After
public void after() {
Screenshot.snap(activity.findViewById(android.R.id.content)).setName(getClass().getName() + "_" + testName.getMethodName()).record();
}
@SuppressWarnings("unchecked")
private Class<ActivityT> getActivityType() {
return (Class<ActivityT>) GenericSuperclassUtils.getTypeArguments(getClass(), PresenterActivityScreenshotTest.class)[0];
}
@SuppressWarnings("unchecked")
private Class<PresenterT> getPresenterType() {
return (Class<PresenterT>) GenericSuperclassUtils.getTypeArguments(getClass(), PresenterActivityScreenshotTest.class)[1];
}
@SuppressWarnings("unchecked")
private Class<ViewT> getViewType() {
return (Class<ViewT>) GenericSuperclassUtils.getTypeArguments(getClass(), PresenterActivityScreenshotTest.class)[2];
}
public static class UiThreadInvocationHandler<ViewT> implements InvocationHandler {
@NonNull
private final UiThreadTestRule uiThreadTestRule;
@NonNull
private final ViewT impl;
public UiThreadInvocationHandler(@NonNull UiThreadTestRule uiThreadTestRule, @NonNull ViewT impl) {
this.uiThreadTestRule = uiThreadTestRule;
this.impl = impl;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final AsyncTaskResult<Object> result = new AsyncTaskResult<>();
final Runnable runnable = new Runnable() {
@Override
public void run() {
try {
result.setResult(method.invoke(impl, args));
} catch (Exception e) {
result.setEx(e);
}
}
};
if (Looper.myLooper() == Looper.getMainLooper()) {
// Already on UI thread
runnable.run();
} else {
uiThreadTestRule.runOnUiThread(runnable);
}
if (null != result.getEx()) {
throw result.getEx();
}
return result.getResult();
}
@SuppressWarnings("unchecked")
@NonNull
public static <ViewT> ViewT newProxyInstance(@NonNull UiThreadTestRule uiThreadTestRule, @NonNull ViewT view, @NonNull Class<ViewT> viewType) {
return (ViewT) Proxy.newProxyInstance(viewType.getClassLoader(), new Class<?>[]{viewType}, new UiThreadInvocationHandler(uiThreadTestRule, view));
}
}
}