Skip to content

Commit

Permalink
Add instrumentation test infrastructure.
Browse files Browse the repository at this point in the history
A custom test runner subclass flips a boolean on a debug module before the graph is created. When the graph does get created, we use that boolean to force mock mode and skip wrapping the debug controls (both because it would never be used but also to ensure view-based matchers of Espresso work). We also kill the keyguard and hold a wake lock so that the screen is on and visible.
  • Loading branch information
JakeWharton committed May 9, 2015
1 parent ea724b1 commit 696c76d
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 4 deletions.
43 changes: 43 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ android {

buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
buildConfigField "String", "BUILD_TIME", "\"${buildTime}\""

testInstrumentationRunner "com.jakewharton.u2020.U2020TestRunner"
}

buildTypes {
Expand All @@ -88,11 +90,41 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}

packagingOptions {
exclude 'LICENSE.txt'
}
}

// TODO remove eventually: http://b.android.com/162285
configurations {
internalDebugCompile

all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'com.android.support') {
// The support test libraries depend on an older version of the support annotations. In
// order to avoid NoClassDefFoundError being thrown we force all versions to the latest.
// Additionally, Espresso has a dependency on an old version of recyclerview-v7 which, for
// whatever reason, trumps the newer version and causes crashes.
// TODO remove when test support libraries depend on latest released support-annotations.
// TODO remove when Espresso depends on the latest released recyclerview-v7.
details.useVersion '22.1.1'
} else if (details.requested.group == 'org.hamcrest') {
// Espresso v2.1 depends on hamcrest-integration and hamcrest-core v1.1. The JUnit override
// below has a transitive dependency to hamcrest-core v1.3 which contains classes previously
// in hamcrest-integration but without its version going to v1.3 we get duplicate classes.
// TODO remove when Espresso depends on JUnit 4.12.
details.useVersion '1.3'
} else if (details.requested.name == 'junit-dep') {
// The support test runner depends on an old version of junit (4.10) named junit-dep. We want
// to use a newer version which is no longer uses this naming. Force all versions to use the
// same artifactId naming and let normal version conflict resolution semantics apply.
// TODO remove when Espresso depends on JUnit 4.12.
details.useTarget group: 'junit', name: 'junit', version: details.requested.version
}
}
}
}

dependencies {
Expand Down Expand Up @@ -120,6 +152,12 @@ dependencies {

compile 'net.danlew:android.joda:2.7.1'
internalCompile 'com.mattprecious.telescope:telescope:1.4.0@aar'

androidTestCompile 'junit:junit:4.12'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1'
androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'
}

def installAll = tasks.create('installAll')
Expand All @@ -129,3 +167,8 @@ android.applicationVariants.all { variant ->
// Ensure we end up in the same group as the other install tasks.
installAll.group = variant.install.group
}

// The default 'assemble' task only applies to normal variants. Add test variants as well.
android.testVariants.all { variant ->
tasks.getByName('assemble').dependsOn variant.assemble
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.jakewharton.u2020;

import android.app.KeyguardManager;
import android.content.Context;
import android.os.PowerManager;
import android.support.test.runner.AndroidJUnitRunner;

import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP;
import static android.os.PowerManager.FULL_WAKE_LOCK;
import static android.os.PowerManager.ON_AFTER_RELEASE;

public final class U2020TestRunner extends AndroidJUnitRunner {
@Override public void onStart() {
// Inform the app we are an instrumentation test before the object graph is initialized.
DebugU2020Module.instrumentationTest = true;

runOnMainSync(new Runnable() {
@SuppressWarnings("deprecation") // We don't care about deprecation here.
@Override public void run() {
Context app = getTargetContext().getApplicationContext();

String name = U2020TestRunner.class.getSimpleName();
// Unlock the device so that the tests can input keystrokes.
KeyguardManager keyguard = (KeyguardManager) app.getSystemService(KEYGUARD_SERVICE);
keyguard.newKeyguardLock(name).disableKeyguard();
// Wake up the screen.
PowerManager power = (PowerManager) app.getSystemService(POWER_SERVICE);
power.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE, name)
.acquire();
}
});

super.onStart();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.jakewharton.u2020.ui;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.os.SystemClock.sleep;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public final class DummyTest {
@Rule public final ActivityTestRule<MainActivity> main =
new ActivityTestRule<>(MainActivity.class);

@Test public void noneOfTheThings() {
sleep(SECONDS.toMillis(5)); // Long enough to see some data from mock mode.
assertTrue(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.jakewharton.u2020.data.DebugDataModule;
import com.jakewharton.u2020.ui.DebugUiModule;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;

@Module(
addsTo = U2020Module.class,
Expand All @@ -13,4 +15,11 @@
overrides = true
)
public final class DebugU2020Module {
// Low-tech flag to force certain debug build behaviors when running in an instrumentation test.
// This value is used in the creation of singletons so it must be set before the graph is created.
static boolean instrumentationTest = false;

@Provides @Singleton @IsInstrumentationTest boolean provideIsInstrumentationTest() {
return instrumentationTest;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jakewharton.u2020;

import java.lang.annotation.Retention;
import javax.inject.Qualifier;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier @Retention(RUNTIME)
public @interface IsInstrumentationTest {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Application;
import android.content.SharedPreferences;
import android.net.Uri;
import com.jakewharton.u2020.IsInstrumentationTest;
import com.jakewharton.u2020.data.api.DebugApiModule;
import com.jakewharton.u2020.data.prefs.BooleanPreference;
import com.jakewharton.u2020.data.prefs.IntPreference;
Expand Down Expand Up @@ -64,8 +65,10 @@ StringPreference provideEndpointPreference(SharedPreferences preferences) {
return new StringPreference(preferences, "debug_endpoint", ApiEndpoints.MOCK_MODE.url);
}

@Provides @Singleton @IsMockMode boolean provideIsMockMode(@ApiEndpoint StringPreference endpoint) {
return ApiEndpoints.isMockMode(endpoint.get());
@Provides @Singleton @IsMockMode boolean provideIsMockMode(@ApiEndpoint StringPreference endpoint,
@IsInstrumentationTest boolean isInstrumentationTest) {
// Running in an instrumentation forces mock mode.
return isInstrumentationTest || ApiEndpoints.isMockMode(endpoint.get());
}

@Provides @Singleton NetworkProxyPreference provideNetworkProxy(SharedPreferences preferences) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jakewharton.u2020.ui;

import com.jakewharton.u2020.IsInstrumentationTest;
import com.jakewharton.u2020.ui.debug.DebugAppContainer;
import com.jakewharton.u2020.ui.debug.DebugView;
import com.jakewharton.u2020.ui.debug.SocketActivityHierarchyServer;
Expand All @@ -17,8 +18,10 @@
overrides = true
)
public class DebugUiModule {
@Provides @Singleton AppContainer provideAppContainer(DebugAppContainer debugAppContainer) {
return debugAppContainer;
@Provides @Singleton AppContainer provideAppContainer(DebugAppContainer debugAppContainer,
@IsInstrumentationTest boolean isInstrumentationTest) {
// Do not add the debug controls for when we are running inside of an instrumentation test.
return isInstrumentationTest ? AppContainer.DEFAULT : debugAppContainer;
}

@Provides @Singleton ActivityHierarchyServer provideActivityHierarchyServer() {
Expand Down

0 comments on commit 696c76d

Please sign in to comment.