Skip to content

tmurakami/dexopener

Repository files navigation

DexOpener

Android CircleCI Release Javadoc

An Android library that provides the ability to mock your final classes on Android devices.

Installation

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

repositories {
    google()
    maven { url 'https://jitpack.io' }
}

dependencies {
    androidTestImplementation 'com.github.tmurakami:dexopener:2.0.5'

    androidTestImplementation 'androidx.test:runner:x.y.z'

    // DexOpener is also usable together with the ATSL runner.
    // androidTestImplementation 'com.android.support.test:runner:x.y.z'
}

Usage

Add an AndroidJUnitRunner subclass into your app's androidTest directory.

// Specify your root package as `package` statement.
// The final classes you can mock are only in the package and its subpackages.
package your.root.pkg;

public class YourAndroidJUnitRunner extends AndroidJUnitRunner {
    @Override
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        DexOpener.install(this); // Call me first!
        return super.newApplication(cl, className, context);
    }
}

Then specify your AndroidJUnitRunner as the default test instrumentation runner in your app's build.gradle.

android {
    defaultConfig {
        minSdkVersion 16 // 16 or higher
        testInstrumentationRunner 'your.root.pkg.YourAndroidJUnitRunner'
    }
}

You can see some examples here.

Tips

Replacing the Application instance for testing

To instantiate your custom android.app.Application object other than default application, pass a string literal of that class name as the second argument to super.newApplication() in your test instrumentation runner.

@Override
public Application newApplication(ClassLoader cl, String className, Context context)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    DexOpener.install(this); // Call me first!
    return super.newApplication(cl, "your.root.pkg.YourTestApplication", context);
}

Do not call Class#getName() to get your Application class name. The following code may cause an IllegalAccessError saying Class ref in pre-verified class ....

return super.newApplication(cl, YourTestApplication.class.getName(), context);

Alternatives

DexOpener will do the following things at runtime:

  1. Remove the final modifier from the classes belonging to the specified package
  2. Create dex files to make the application class loader load those classes

However, they are not so lightweight. If you would like to save even a little testing time of your Kotlin app, you can introduce the all-open compiler plugin instead of DexOpener.

This article would be helpful to know how to open Kotlin classes with that plugin only for testing.

You can now even stub the final methods of the Android API using dexmaker-mockito-inline library. In addition, dexmaker-mockito-inline-extended library supports for stubbing static methods and spying on an object created by the Android system such as Activity. Here is an introduction article.

Note that these libraries will only work with Android 9+.

MockK provides inline mocking feature derived from Dexmaker. You would be able to mock the final classes by using mockk-android library. That feature is automatically enabled with Android 9+. You can see the supported features here.

By checking Build.VERSION.SDK_INT in your test instrumentation runner, you can switch that feature and DexOpener according to the OS version of the testing device. See the test instrumentation runner in the mockk example.

Notice

DexOpener contains the classes of the following libraries:

They have been minified with ProGuard and repackaged with Jar Jar Links.

License

Copyright 2016 Tsuyoshi Murakami

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.