Markus K
Markus K

Reputation: 1653

Ignoring Android unit tests depending on SDK level

Is there an annotation or some other convenient way to ignore junit tests for specific Android SDK versions? Is there something similar to the Lint annotation TargetApi(x)? Or do I manually have to check whether to run the test using the Build.VERSION?

Upvotes: 8

Views: 4718

Answers (5)

Phileo99
Phileo99

Reputation: 5659

I think @mark.w's answer is the path of least resistance:

You might wanna take a look at SdkSuppress annotation. It has two methods -- maxSdkVersion and minSdkVersion which you could use depending on your need.

for example:

@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
public void testMethodThatRunsConditionally() {
    System.out.print("Test me!");
}

Upvotes: 1

Georgiy Chebotarev
Georgiy Chebotarev

Reputation: 3230

I have upgraded answer of @Enrichman for Kotlin modern version. So, this is test runner class:

class ConditionalSDKTestRunner(klass: Class<*>?) : BlockJUnit4ClassRunner(klass) {
    override fun runChild(method: FrameworkMethod, notifier: RunNotifier) {
        // Annotation class could not have a base class of interface, so we can not group them.
        val testOnlyForTargetSDKCode = method.getAnnotation(TestOnlyForTargetSDK::class.java)?.sdkLevel?.code
        val testForTargetSDKAndBelowCode = method.getAnnotation(TestForTargetSDKAndBelow::class.java)?.sdkLevel?.code
        val testForTargetSDKAndAboveCode = method.getAnnotation(TestForTargetSDKAndAbove::class.java)?.sdkLevel?.code

        when {
            // If annotation exists, but target SDK is not equal of emulator SDK -> skip this test.
            testOnlyForTargetSDKCode != null && testOnlyForTargetSDKCode != Build.VERSION.SDK_INT ->
                notifier.fireTestIgnored(describeChild(method))
            // If annotation exists, but test SDK is lower than emulator SDK -> skip this test.
            testForTargetSDKAndBelowCode != null && testForTargetSDKAndBelowCode < Build.VERSION.SDK_INT ->
                notifier.fireTestIgnored(describeChild(method))
            // If annotation exists, but test SDK is higher than emulator SDK -> skip this test.
            testForTargetSDKAndAboveCode != null && testForTargetSDKAndAboveCode > Build.VERSION.SDK_INT ->
                notifier.fireTestIgnored(describeChild(method))
            // For other cases test should be started.
            else -> super.runChild(method, notifier)
        }
    }
}

Enum with exist SDKs in your project:

enum class SdkLevel(val code: Int) {
    SDK_24(24),
    SDK_25(25),
    SDK_26(26),
    SDK_27(27),
    SDK_28(28),
    SDK_29(29),
    SDK_30(30),
    SDK_31(31),
    SDK_32(32)
}

and annotations below:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TestForTargetSDKAndAbove(val sdkLevel: SdkLevel)

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TestForTargetSDKAndBelow(val sdkLevel: SdkLevel)

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TestOnlyForTargetSDK(val sdkLevel: SdkLevel)

To use it just add ConditionalSDKTestRunner to your base android unit test class:

@RunWith(ConditionalSDKTestRunner::class)
abstract class BaseAndroidUnitTest

and necessary annotation for test to make it actual only for special sdk:

@Test
@TestForTargetSDKAndAbove(SdkLevel.SDK_31)
fun getConnectionInfo_positive_SDK31() {

@Test
@TestForTargetSDKAndBelow(SdkLevel.SDK_30)
fun getConnectionInfo_negative1_SDK30() {

@Test
@TestOnlyForTargetSDK(SdkLevel.SDK_29)
fun getConnectionInfo_negative1_SDK29() {

That's all. Thanks!

Upvotes: 0

Thomas Keller
Thomas Keller

Reputation: 6060

An alternative is to use JUnit's assume functionality:

@Test
fun shouldWorkOnNewerDevices() {
   assumeTrue(
       "Can only run on API Level 23 or newer because of reasons",
       Build.VERSION.SDK_INT >= 23
   )
}

If applied, this effectively marks the test method as skipped.

This is not so nice like the annotation solution, but you also don't need a custom JUnit test runner.

Upvotes: 4

Roy Clarkson
Roy Clarkson

Reputation: 2179

I've been searching for an answer to this question, and haven't found a better way than to check the version. I was able to conditionally suppress the execution of test logic by putting a check in the following Android TestCase methods. However, this doesn't actually prevent the individual tests from executing. Overriding the runTest() method like this will cause tests to "pass" on API levels you know will not work. Depending on your test logic, you may want to override tearDown() too. Maybe someone will offer a better solution.

@Override
protected void setUp() throws Exception {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
        if (Log.isLoggable(TAG, Log.INFO)) {
            Log.i(TAG, "This feature is only supported on Android 2.3 and above");
        }
    } else {
        super.setUp();
    }
}

@Override
protected void runTest() throws Throwable {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
        assertTrue(true);
    } else {
        super.runTest();
    }
}

Upvotes: 2

Enrichman
Enrichman

Reputation: 11337

I don't think there is something ready but it pretty easy to create a custom annotation for this.

Create your custom annotation

@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME)
public @interface TargetApi {
    int value();
}

Ovverride the test runner (that will check the value and eventually ignore/fire the test)

public class ConditionalTestRunner extends BlockJUnit4ClassRunner {
    public ConditionalTestRunner(Class klass) throws InitializationError {
        super(klass);
    }

    @Override
    public void runChild(FrameworkMethod method, RunNotifier notifier) {
        TargetApi condition = method.getAnnotation(TargetApi.class);
        if(condition.value() > 10) {
            notifier.fireTestIgnored(describeChild(method));
        } else {
            super.runChild(method, notifier);
        }
    }
}

and mark your tests

@RunWith(ConditionalTestRunner.class)
public class TestClass {

    @Test
    @TargetApi(6)
    public void testMethodThatRunsConditionally() {
        System.out.print("Test me!");
    }
}

Just tested, it works for me. :)

Credits to: Conditionally ignoring JUnit tests

Upvotes: 11

Related Questions