17

Is there a runtime check for an application to find out if it runs as part of an instrumentation test?

Background: Our application performs a database sync when starting. But that should happen only when started regularly. It especially interferes with the instrumentation tests testing the db sync. Not surprisingly.

And with all the other tests it's just a waste of CPU cycles.

Juozas Kontvainis
  • 9,461
  • 6
  • 55
  • 66
Martin
  • 11,577
  • 16
  • 80
  • 110

8 Answers8

12

A much simpler solution is check for a class that would only be present in a test classpath, works with JUnit 4 (unlike the solution using ActivityUnitTestCase) and doesn't require to send custom intents to your Activities / Services (which might not even be possible in some cases)

private boolean isTesting() {
    try {
        Class.forName("com.company.SomeTestClass");
        return true;
    } catch (ClassNotFoundException e) {
        return false;
    }
}
Yannick Menager
  • 305
  • 2
  • 9
7

If you are using Robolectric, you can do something like this:

public boolean isUnitTest() {
        String device = Build.DEVICE;
        String product = Build.PRODUCT;
        if (device == null) {
            device = "";
        }

        if (product == null) {
            product = "";
        }
        return device.equals("robolectric") && product.equals("robolectric");
    }
Dennis Allert
  • 580
  • 1
  • 9
  • 8
6

Since API Level 11, the ActivityManager.isRunningInTestHarness() method is available. This might do what you want.

Edward Dale
  • 29,597
  • 13
  • 90
  • 129
  • 9
    It's returning false for me whichever I am running tests or app configuration – Piotr Apr 29 '14 at 22:25
  • This method is basically only used by system apps when they need to figure out whether they run in some platform test harness. See my bug here: http://b.android.com/191171 – Thomas Keller Feb 25 '16 at 22:10
  • According to updated docs, `ActivityManager.isRunningInUserTestHarness()` should be used instead (but its worth nothing that it's only available in `API 29`, not even in support/androidx libs). – Top-Master Aug 13 '20 at 19:28
2

d= (◕‿↼ ) Great answer, but if some library developer (like me) wants to know if the Host (or App using the library) is being tested, then try:

import android.content.pm.ApplicationInfo;

// ...

private static int wasTestRun = 0xDEAD;

/**
 * Should only be used to speed up testing (no behavior change).
 * @return true in tests, if Gradle has the right dependencies.
 */
public static boolean isTestRun(@NonNull Context context) {
    if (wasTestRun != 0xDEAD) {
        return wasTestRun != 0;
    }
    // Ignore release builds (as App may be using JUnit by mistake).
    if (isDebuggable(context)) {
        try {
            Class.forName("org.junit.runner.Runner");
            wasTestRun = 1;
            return true;
        } catch (ClassNotFoundException ignored) {
        }
    }
    wasTestRun = 0;
    return false;
}

public static boolean isDebuggable(@Nullable Context context) {
    return context != null && (context.getApplicationContext()
            .getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

Note that I am not using any AtomicBoolean or other helpers, as it is already pretty fast (and locking may just bring the speed down).

Top-Master
  • 7,611
  • 5
  • 39
  • 71
2

If you're using ActivityUnitTestCase, you could set a custom Application object with setApplication, and have a flag in there to switch database sync on or off? There's an example of using a custom Application object on my blog:

http://www.paulbutcher.com/2011/03/mock-objects-on-android-with-borachio-part-3/

Paul Butcher
  • 10,722
  • 3
  • 40
  • 44
  • 1
    Interesting. I already have an custom Application where I could easily a instrumentation test flag. I'll try it tomorrow. – Martin Jul 24 '11 at 09:06
  • Something else to think of - you could use RoboGuice http://code.google.com/p/roboguice/ to inject a test-only version of your db sync code as appropriate (this is actually what we do in our test code). There's a description of our setup here: http://www.swiftkey.net/blog/?p=496 – Paul Butcher Jul 24 '11 at 13:46
  • It seems that only Activity and Service test cases have a `setApplication` method and simple `AndroidTestCase` won't. But then they seem to run ok anyway. Anyway it seems the only open. thnaks. – Martin Aug 03 '11 at 07:34
  • If you need something more flexible, RoboGuice definitely also works. Although there is quite a bit of mucking-around to get it set up :-) – Paul Butcher Aug 03 '11 at 08:48
1

You can pass an intent extra to your activity indicating it's under test.

1) In your test, pass "testMode" extra to your activity:

public void setUp() throws Exception {
    super.setUp();

    Intent activityIntent = new Intent();
    activityIntent.putExtra("testMode", true);
    setActivityIntent(activityIntent);
}

2) In your activity, check for testMode:

Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("testMode")) {
    // disable your database sync
}
Eduard
  • 3,482
  • 2
  • 27
  • 45
  • I use this method for some legacy code testing. I would point out though that your production code should generally not handle testing in any way. – Pär Nils Amsen Jun 14 '17 at 07:55
0

You can try this

if (isRunningTest == null) {
        isRunningTest = false;
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        List<StackTraceElement> list = Arrays.asList(stackTrace);
        for (StackTraceElement element : list) {
            if (element.getClassName().startsWith("androidx.test.runner.MonitoringInstrumentation")) {
                isRunningTest = true;
                break;
            }
        }
    }
Hamady C.
  • 1,205
  • 11
  • 13
-1

This work for me because no actual device is running

public static boolean isUnitTest() {
    return Build.BRAND.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.PRODUCT.startsWith(Build.UNKNOWN);
}
Allen Chan
  • 4,041
  • 2
  • 19
  • 16
  • Not sure that you need to test twice for DEVICE. On my test machine with AS 2.3.3 and embedded JDK, BRAND, DEVICE and PRODUCT are null, the test becomes: return (Build.BRAND == null || Build.BRAND.startsWith(Build.UNKNOWN)) && (Build.DEVICE == null || Build.DEVICE.startsWith(Build.UNKNOWN)) && (Build.PRODUCT == null || Build.PRODUCT.startsWith(Build.UNKNOWN)); – Timores Jun 09 '17 at 14:35