Still being relatively green to things, I'm trying to figure out if it is possible to write JUnit functional tests against a prebuilt app sans source. I've found a means of re-signing the app but I'd like to know how to include it in an Eclipse project as a dependency of my tests?
Yes, this is indeed possible using Java reflections.
Besides this, it can be necessary to resign the app you want to test. Download it from your device (e.g., using the app AppMonster), resign it using the resign.jar (http://www.troido.de/re-sign.jar) and install it again using adb.
Afterwards, everything should work fine.
#SuppressWarnings("rawtypes")
public class YourTestSuite extends ActivityInstrumentationTestCase2 {
/** Package ID of the app under test. */
private static final String TARGET_PACKAGE_ID = "com.something";
/** ID of the app under test's main activity. */
private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = "com.something.HomeActivity";
/** Launcher activity class. */
private static Class<?> launcherActivityClass;
/** Static code to find the activity class. */
static {
try {
launcherActivityClass = Class
.forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME);
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Constructor
*
* #throws ClassNotFoundException
*/
#SuppressWarnings("unchecked")
public YourTestSuite() throws ClassNotFoundException {
super(TARGET_PACKAGE_ID, launcherActivityClass);
}
/* Your test code comes here. */
}
Related
I was trying to check on implementation of few methods inside firebaseInstanceId class but I was routed to generated stub file instead.
public class FirebaseInstanceId {
private static java.util.Map<java.lang.String,com.google.firebase.iid.FirebaseInstanceId> zzbhH;
private static com.google.firebase.iid.zze zzclh;
private final com.google.firebase.FirebaseApp zzcli;
private final com.google.firebase.iid.zzd zzclj;
private final java.lang.String zzclk;
public static com.google.firebase.iid.FirebaseInstanceId getInstance() { /* compiled code */ }
#android.support.annotation.Keep
public static synchronized com.google.firebase.iid.FirebaseInstanceId getInstance(#android.support.annotation.NonNull com.google.firebase.FirebaseApp firebaseApp) { /* compiled code */ }
private FirebaseInstanceId(com.google.firebase.FirebaseApp firebaseApp, com.google.firebase.iid.zzd zzd) { /* compiled code */ }
java.lang.String zzabM() { /* compiled code */ }
public java.lang.String getId() { /* compiled code */ }
public long getCreationTime() { /* compiled code */ }
You need to enable Java Bytecode Decompiler from Android studio.
To enable decompiler follow below step
Press shift 2 times. the popup will appear then search for Java Bytecode Decompiler. just like below image.
Bingo now you can check stub code!!
If you have not downloaded source code for SDK tools then you can download it from SDK manager in Android studio.
Java compiler does not preserve parameter names for any interface, unless newer compiler option -parameter is used (I am not sure how to use it with android studio) - refer example below.
Since java compiler does not preserve parameters names, Android Studio "code -> implement methods" is not able to generate code with original parameter names.
The question is, how to implement a library module so that Android Studio Menu, Code->Implement Methods correctly generates code with all the original parameter names.
For example, following is a simple class and an interface. This class is in a separate aar module. When application uses this AAR, implements TablaListener and asks AndroidStudio to generate interface methods stubs, the parameter names are not preserved.
Please note that proguard is NOT used.
Any ideas?
public class TablaCore {
public interface TablaListener {
/**
* #param params
* #param data
* #return
*/
boolean TablaCore_onAction(String params, byte[] data);
}
private static TablaListener mListener = null;
public static void setListener(TablaListener myListener) {
mListener = myListener;
}
public TablaListener getListener() {
return mListener;
}
}
It is easy to demonstrate by compiling and decompiling above class. This is decompiled version
public class TablaCore
{
private static TablaListener mListener = null;
public static void setListener(TablaListener myListener)
{
mListener = myListener;
}
public TablaListener getListener()
{
return mListener;
}
public static abstract interface TablaListener
{
public abstract boolean TablaCore_onAction(String paramMessageParams, byte[] paramArrayOfByte);
}
}
You have to include Android SDK source code.
Go to: File > Settings... > Apperance & Behavior > System Settings > Android SDK
On the tab SDK Platforms select Show Package Details and find and select appropriate Sources for Android XX. Unfortunately, currently for Android API 27, there is no sources, but there is for Sources for Android 26. Apply changes - the download window should appear automatically.
After downloading and restarting implementing methods should use proper names for method parameters.
I need to determine in runtime from code if the application is run under TestInstrumentation.
I could initialize the test environment with some env/system variable, but Eclipse ADK launch configuration would not allow me to do that.
Default Android system properties and environment do not to have any data about it. Moreover, they are identically same, whether the application is started regularly or under test.
This one could be a solution: Is it possible to find out if an Android application runs as part of an instrumentation test but since I do not test activities, all proposed methods there won't work. The ActivityManager.isRunningInTestHarness() method uses this under the hood:
SystemProperties.getBoolean("ro.test_harness")
which always returns false in my case. (To work with the hidden android.os.SystemProperties class I use reflection).
What else can I do to try to determine from inside the application if it's under test?
I have found one hacky solution: out of the application one can try to load a class from the testing package. The appication classloader surprisingly can load classes by name from the testing project if it was run under test. In other case the class is not found.
private static boolean isTestMode() {
boolean result;
try {
application.getClassLoader().loadClass("foo.bar.test.SomeTest");
// alternatively (see the comment below):
// Class.forName("foo.bar.test.SomeTest");
result = true;
} catch (final Exception e) {
result = false;
}
return result;
}
I admit this is not elegant but it works. Will be grateful for the proper solution.
The isTestMode() solution did not work for me on Android Studio 1.2.1.1. Almighty Krzysztof from our company tweaked your method by using:
Class.forName("foo.bar.test.SomeTest");
instead of getClassLoader(). Thanks for Krzysztof!
We created a solution to pass parameters to the MainActivity and use it inside the onCreate method, enabling you to define how the Activity will be created.
In MainActivity class, we created some constants, which could also be an enum. We created a static attribute too.
public class MainActivity {
public static final int APPLICATION_MODE = 5;
public static final int UNIT_TEST_MODE = 10;
public static final int OTHER_MODE = 15;
public static int activityMode = APPLICATION_MODE;
(...)
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
switch (activityMode) {
case OTHER_MODE:
(...)
break;
case UNIT_TEST_MODE:
Log.d(TAG, "Is in Test Mode!");
break;
case APPLICATION_MODE:
(...)
break;
}
(...)
}
(...)
}
We made MainActivityTest class abstract, created a setApplicationMode and called this method inside the setUp() method, before calling the super.setUp() method.
public abstract class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
protected void setUp() throws Exception {
setApplicationMode(); // <=====
super.setUp();
getActivity();
(...)
}
(...)
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.UNIT_TEST_MODE;
}
}
All other test classes inherit from MainActivityTest, if we want it to have another behaviour, we can simply override the setApplicationMode method.
public class OtherMainActivityTest extends MainActivityTest {
(...)
#Override
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.OTHER_MODE;
}
}
The user nathan-almeida is the friend that is co-author of this solution.
If I write a custom Shadow for my Activity, and registering it with RobolectricTestRunner, will the framework intercept the Activity with my custom Shadow whenever it's started?
Thanks.
The short answer is no.
Robolectric is selective about what classes it intercepts and instruments. At the time of this writing, the only classes that will be instrumented must have a fully qualified classname match one of these selectors:
android.*
com.google.android.maps.*
org.apache.http.impl.client.DefaultRequestDirector
The whole reason for Robolectric's existence is that the classes provided in the Android SDK jar throw exceptions when invoked in a JVM (i.e. not on an emulator or device). Your application's Activity has source that is not 'hostile' (it probably does not throw exceptions when the methods or constructors are invoked). Robolectric's intended purpose is to allow you to put your application's code under test, which would otherwise not be possible due to the way the SDK is written. Some of the other reasons why Robolectric was created were:
The SDK does not always have methods that would allow you to query the state of the Android objects manipulated by your application's code. Shadows can be written to provide access to this state.
Many of the classes and methods in the Android SDK are final and/or private or protected, making it difficult to create the dependencies needed by your application code that would otherwise be available to your application code.
The code could clearly be changed to shadow any class. There has been talk in the past about extracting the shadowing features into a standalone library, to assist writing tests using some other test-hostile api.
Why do you want to shadow your Activity?
This has significantly changed with Robolectric 2. You can specify custom shadows in the configuration instead of writing your own TestRunner.
For example:
#Config(shadows = {ShadowAudioManager.class, ShadowContextWrapper.class})
Yes, if you subclass the RobolectricTestRunner, add a custom package to the constructor and load your Shadow classes in the bindShadowClasses method. No need to use the android.* package trick.
(Note: this is with robolectric-1.1)
There are a number of hooks provided in the RobolectricTestRunner#setupApplicationState that you can override.
Here's my implementation of the RobolectricTestRunner.
import org.junit.runners.model.InitializationError;
import com.android.testFramework.shadows.ShadowLoggerConfig;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.RobolectricTestRunner;
public class RoboRunner extends RobolectricTestRunner {
public RoboRunner(Class<?> clazz) throws InitializationError {
super(clazz);
addClassOrPackageToInstrument("package.you're.creating.shadows.of");
}
#Override
protected void bindShadowClasses() {
super.bindShadowClasses(); // as you can see below, you really don't need this
Robolectric.bindShadowClass(ShadowClass.class);
}
}
More methods you can subclass (from RobolectricTestRunner.class)
/**
* Override this method to bind your own shadow classes
*/
protected void bindShadowClasses() {
}
/**
* Override this method to reset the state of static members before each test.
*/
protected void resetStaticState() {
}
/**
* Override this method if you want to provide your own implementation of Application.
* <p/>
* This method attempts to instantiate an application instance as specified by the AndroidManifest.xml.
*
* #return An instance of the Application class specified by the ApplicationManifest.xml or an instance of
* Application if not specified.
*/
protected Application createApplication() {
return new ApplicationResolver(robolectricConfig).resolveApplication();
}
Here's where they're called in the Robolectric TestRunner:
public void setupApplicationState(final RobolectricConfig robolectricConfig) {
setupLogging();
ResourceLoader resourceLoader = createResourceLoader(robolectricConfig);
Robolectric.bindDefaultShadowClasses();
bindShadowClasses();
resourceLoader.setLayoutQualifierSearchPath();
Robolectric.resetStaticState();
resetStaticState();
DatabaseConfig.setDatabaseMap(this.databaseMap);//Set static DatabaseMap in DBConfig
Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
}
As an update, I have been able to create shadows of my own classes, as long as am careful to bind the shadow class before any possible loader acts on that class. So, per the instructions, in the RoboRunner I did:
#Override protected void bindShadowClasses() {
Robolectric.bindShadowClass(ShadowLog.class);
Robolectric.bindShadowClass(ShadowFlashPlayerFinder.class);
}
Did I mention that I'm cheating a bit? The original answer above is (of course) correct. So I use this for my real class:
package android.niftyco;
public class FlashPlayerFinder {
.. .
And my mock (shadow) is in back in my test package, as one might expect:
package com.niftyco.android.test;
#Implements(FlashPlayerFinder.class)
public class ShadowFlashPlayerFinder {
#RealObject private FlashPlayerFinder realFPF;
public void __constructor(Context c) {
//note the construction
}
#Implementation
public boolean isFlashInstalled() {
System.out.print("Let's pretend that Flash is installed\n");
return(true);
}
}
Might be late, but from here: org.robolectric.bytecode.Setup, you might find further detail about what classes are instrumented.
public boolean shouldInstrument(ClassInfo classInfo) {
if (classInfo.isInterface() || classInfo.isAnnotation() || classInfo.hasAnnotation(DoNotInstrument.class)) {
return false;
}
// allow explicit control with #Instrument, mostly for tests
return classInfo.hasAnnotation(Instrument.class) || isFromAndroidSdk(classInfo);
}
public boolean isFromAndroidSdk(ClassInfo classInfo) {
String className = classInfo.getName();
return className.startsWith("android.")
|| className.startsWith("libcore.")
|| className.startsWith("dalvik.")
|| className.startsWith("com.android.internal.")
|| className.startsWith("com.google.android.maps.")
|| className.startsWith("com.google.android.gms.")
|| className.startsWith("dalvik.system.")
|| className.startsWith("org.apache.http.impl.client.DefaultRequestDirector");
}
I know how to test Activity classes with JUnit 4 in Android but I am unable to understand how to test non-activity classes (which don't extends Activity, ListActivity, or some other Activity class, but uses some Android APIs). Please help me in this regard.
To test non activity classes:
create a test project
create a test case
run as Android JUnit Test
public class MyClassTests extends TestCase {
/**
* #param name
*/
public myClassTests(String name) {
super(name);
}
/* (non-Javadoc)
* #see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
}
/* (non-Javadoc)
* #see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
}
/**
* Test something
*/
public final void testSomething() {
fail("Not implemented yet");
}
}
The Android SDK includes JUnit. In fact, the Android test classes such as AndroidTestCase and InstrumentationTestCase inherit from junit.framework.TestCase. This means that you can use a standard JUnit test case to test a non-Activity class and include it in Android Projects.
For example, you can create an Android Project with a simple class to test:
public class MyClass {
public static int getOne() {
return 1;
}
}
and an Android Test Project with a standard JUnit test to test this class:
public class TestMyClass extends TestCase {
public void testMyClass() {
assertEquals(1, MyClass.getOne());
}
}
and run this on an Android device or on the Android emulator.
More information after seeing clarification of question in the comments:
AndroidTestCase or other Android test classes can be used to test non-Activity classes which need access to the rest of the Android framework (with a dummy activity provided in the setUp() if necessary). These give you access to a Context if you need to, for example, bind to a service.