33

I am writing some Espresso tests for Android. I am running in the the following problem:

In order for a certain test case to run properly, I need to disable some features in the app. Therefore, in my app, I need to detect whether I am running Espresso test so that I can disable it. However, I don't want to use BuildConfig.DEBUG to because I don't want those features to be disabled in a debug build. Also, I would like to avoid creating a new buildConfig to avoid too many build variants to be created (we already have a lot of flavors defined).

I was looking for a way to define buildConfigField for test but I couldn't find any reference on Google.

Comtaler
  • 1,580
  • 1
  • 15
  • 26

10 Answers10

35

Combining Commonsware comment + Comtaler's solution here's a way to do it for any test class using the Espresso framework.

private static AtomicBoolean isRunningTest;

public static synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            // "android.support.test.espresso.Espresso" if you haven't migrated to androidx yet
            Class.forName ("androidx.test.espresso.Espresso");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get();
}
okmanideep
  • 960
  • 7
  • 23
Ryhan
  • 1,815
  • 1
  • 18
  • 22
  • 11
    One more thing, after migration to androidX should be -> `Class.forName("androidx.test.espresso.Espresso")` – walkmn Oct 29 '19 at 15:06
34

Combined with CommonsWare's comment. Here is my solution:

I defined an AtomicBoolean variable and a function to check whether it's running test:

private AtomicBoolean isRunningTest;

public synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            Class.forName ("myApp.package.name.test.class.name");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get ();
}

This avoids doing the try-catch check every time you need to check the value and it only runs the check the first time you call this function.

Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
Comtaler
  • 1,580
  • 1
  • 15
  • 26
  • 3
    What about just using a Boolean (not boolean) object instead of AtomicBoolean class? – Paul Jul 27 '17 at 12:00
  • 3
    This use of `AtomicBoolean` is not thread-save. You need to use the setters of one single final `AtomicBoolean` instead of checking for `null` and creating a new one. Currently, this is just as save as using a normal `boolean`. – ByteHamster May 27 '18 at 16:24
  • But if the method is synchronized then a volatile Boolean (volatile for visibility) should be sufficient and perfectly thread safe, right? – Danilo Bargen Mar 17 '21 at 16:38
28

How about a flag in BuildConfig class?

android {
    defaultConfig {
        // No automatic import :(
        buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "IS_TESTING", "new java.util.concurrent.atomic.AtomicBoolean(false)"
    }
}

Add this somewhere in your test classes.

static {
    BuildConfig.IS_TESTING.set(true);
}
KenIchi
  • 1,129
  • 10
  • 22
  • 3
    This helped me to avoid writing a lot of code just for one line that needs to be different when testing – El_o.di May 14 '19 at 08:32
26

Building on the answers above the following Kotlin code is equivalent:

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("android.support.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

You can then check the value of the property:

if (isRunningTest) {
  // Espresso only code
}
David Pacheco
  • 429
  • 5
  • 5
5

i prefer not to use reflection which is slow on android. Most of us have dagger2 set up for dependency injection. I have a test component set up for testing. Here is a brief way you can get the application mode (testing or normal):

create a enum:

public enum ApplicationMode {
    NORMAL,TESTING;
}

and a normal AppModule:

@Module
public class AppModule {

    @Provides
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.NORMAL;
    }
}

create a test runner like me:

public class PomeloTestRunner extends AndroidJUnitRunner {

    @Override
    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            return super.newApplication(cl, MyTestApplication.class.getName(), context);
    }
}

dont forget to declare it in gradle like this:

defaultConfig {
testInstrumentationRunner "com.mobile.pomelo.base.PomeloTestRunner"
}

Now create a subclass of the AppModule with override method that looks exactly like this and do not mark it as a module above the class definition :

public class TestAppModule extends AppModule{

    public TestAppModule(Application application) {
        super(application);
    }

    @Override
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.TESTING; //notice we are testing here
    }
}

now in your MyTestApplication class that you declared in custom test runner have the following declared:

public class PomeloTestApplication extends PomeloApplication {

    @Singleton
    @Component(modules = {AppModule.class})
    public interface TestAppComponent extends AppComponent {
        }

    @Override
    protected AppComponent initDagger(Application application) {
        return DaggerPomeloTestApplication_TestAppComponent.builder()
                .appModule(new TestAppModule(application)) //notice we pass in our Test appModule here that we subclassed which has a ApplicationMode set to testing
                .build();
    }
}

Now to use it simply inject it in production code wherever like this:

@Inject
    ApplicationMode appMode;

so when your running espresso tests it will be testing enum but when in production code it will be normal enum.

ps not necessary but if you need to see how my production dagger builds the graph its like this and declared in application subclass:

 protected AppComponent initDagger(Application application) {
        return DaggerAppComponent.builder()
                .appModule(new AppModule(application))
                .build();
    }
j2emanue
  • 60,549
  • 65
  • 286
  • 456
4

If you are using JitPack with kotlin. You need to change Espresso's package name .

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("androidx.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

For checking

if (isRunningTest) {
  // Espresso only code
}
adiga
  • 34,372
  • 9
  • 61
  • 83
hemantsb
  • 2,049
  • 2
  • 20
  • 29
2

I'll create two files like below

src/main/.../Injection.java

src/androidTest/.../Injection.java

And in Injection.java I'll use different implementation, or just a static variable int it.

Since androidTest is the source set, not a part of build type, I think what you want to do is hard.

Community
  • 1
  • 1
Nevin Chen
  • 1,815
  • 16
  • 19
  • I doubt that'll cause build error when testing if the 2 files were put into the same package... – KenIchi Feb 26 '19 at 01:04
1

Another simple solution would be to add the Android test runner dependency on the app code and check if you can retrieve the context using InstrumentationRegistry, here's how I did it:

val isAndroidTest = try {
        InstrumentationRegistry.getInstrumentation().context != null
    } catch (e: IllegalStateException) {
        false
    }

The try-catch is necessary since getInstrumentation() would throw an error when the app is not running through the tests.

Abdallah Alaraby
  • 2,222
  • 2
  • 18
  • 30
0

Here is a way to adapt the accepted solution for a react-native Android App.

// MainActivity.java

// ...

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {

      // ...

      @Override
      protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        boolean testingInProgress;

        try {
          Class.forName ("androidx.test.espresso.Espresso");
          testingInProgress = true;
        } catch (ClassNotFoundException e) {
          testingInProgress = false;
        }

        initialProperties.putBoolean("testingInProgress", testingInProgress);

        return initialProperties;
      }
    };
  }
}

You will then be able to access testingInProgress as a prop given to your top-most component (typically App.js). From there you can use componentDidMount or equivalent to access it and throw it into your Redux store (or whatever you are using) in order to make it accessible to the rest of your app.

We use this to trigger some logic in our app to assist us taking screenshots with fastlane.

Pend
  • 662
  • 7
  • 12
0

I would suggest using a boolean variable initialized to false in another class called, for instance, Settings.java:

private static boolean isRunningAndroidTest = false;

This boolean variable would have following setter and getter also defined in Settings.java:

public static void setIsRunningAndroidTest(boolean isRunningAndroidTest) {
    Settings.isRunningAndroidTest = isRunningAndroidTest;
}

public static boolean getIsRunningAndroidTest() {
    return isRunningAndroidTest;
}

One could then toggle this isRunningAndroidTest variable to true at the beginning of the androidTest file by calling the setter defined in Settings.java as follows:

Settings.setIsRunningAndroidTest(true);

Finally, the actual value of this boolean variable can later be checked in any other files by calling its corresponding getter defined in Settings.java as follows:

if (Settings.getIsRunningAndroidTest()) {
    // Do something in case an androidTest is currently running
} else {
    // Do something else in case NO androidTest is currently running
}