问题
Following on from the other questions in my Android TDD series, I have managed to get as far as using Robolectric, Mockito, Maven and ABS to unit test my Android developments. Evidently, I am seemingly pushing my own knowledge boundaries but the Android CI dream is just too alluring. If you can assist with my next problem I would be very grateful, here it is;
I am wanting to write an integration test case which takes my application's database from v1 to head. I am using Dagger for DI and to facilitate this non-standard JUnit test I am injecting the class required to perform the upgrade into my test, as so;
@RunWith(RobolectricTestRunner.class)
public class TestDatabaseHelper {
private static final String V1_DATABASE_SQL = "res/test-support/v1-database/v1-database.sql";
@Inject private UpgradeAuditService upgradeAuditService;
@Rule public StoutLoggingRule loggingRule = new StoutLoggingRule();
@Rule public DaggerInjector injector = new DaggerInjector();
/**
* Tests upgrading the database from version 1 to HEAD.
*
* @throws NameNotFoundException When there is no app but it's running the app. A WTF moment.
* @throws IOException when something goes wrong.
*/
@Test
@Config(manifest="../OceanLife/AndroidManifest.xml")
public void testUpgradingToHead() throws NameNotFoundException, IOException {
// Given.
final Context testApplicationContext = Robolectric.getShadowApplication().getApplicationContext();
final SQLiteDatabase database = SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE);
final int versionCode = testApplicationContext.getPackageManager().getPackageInfo("com.oceanlife", 0).versionCode;
// When.
replaceDatabase(database);
new DatabaseHelper(testApplicationContext, upgradeAuditService).onUpgrade(database, 1, versionCode);
// Then.
final int databaseVersion = SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE).getVersion();
assertEquals("Database was not upgraded.", versionCode, databaseVersion);
}
Important points to note;
- The test lives under it's own test project, separate to the project under test (see my linked questions for structure)
- The failure (stack trace below) comes about when creating the real application's module graph.
- The injector rule (described below) links to the test project Module which includes the module used when running my application for real.
What's happening under the DaggerInjector
rule?
I attempt to inject any dependencies required by my test.
public class DaggerInjector implements MethodRule {
/**
* @see org.junit.rules.MethodRule#apply(org.junit.runners.model.Statement, org.junit.runners.model.FrameworkMethod, java.lang.Object)
*/
@Override
public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
final ObjectGraph graph = ObjectGraph.create(new OceanLifeTestingModule());
graph.inject(target);
base.evaluate();
}
};
}
}
What does your testing module look like?
Adds entry points (this is dagger-0.9.1
, not the latest where inject
replaces entryPoints
) for the tests and (to-be) test mocks replacements.
/**
* The injection container providing information on
* how to construct test support components.
*
* @author David C Branton
*/
@Module(includes = OceanLifeModule.class,
entryPoints = {FixtureBuilder.class,
TestSpotService.class,
TestDatabaseHelper.class},
complete = true,
overrides = true)
public class OceanLifeTestingModule {
/**
* Constructs this module.
*/
public OceanLifeTestingModule() { }
/**
* Provide the database.
*
* @return the application database.
*/
@Provides SQLiteDatabase provideDatabase() {
return SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE);
}
}
My tests run green if I comment out the line of code responsible for creating my real application's object graph. Here is the stack trace;
Error 1: Caught and swallowed from my test perspective
dagger.internal.RuntimeAggregatingPlugin#getModuleAdapter
java.lang.RuntimeException: Unexpected failure loading com.oceanlife.OceanLifeModule$ModuleAdapter
Error 2: Dumped out into the console by Robolectric
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:653)
at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:460)
at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:286)
at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:222)
at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69)
at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52)
at java.lang.Class.initAnnotationsIfNecessary(Class.java:3079)
at java.lang.Class.getAnnotation(Class.java:3038)
at dagger.internal.plugins.reflect.ReflectivePlugin.getModuleAdapter(ReflectivePlugin.java:51)
at dagger.internal.RuntimeAggregatingPlugin.getModuleAdapter(RuntimeAggregatingPlugin.java:98)
at dagger.internal.RuntimeAggregatingPlugin.getAllModuleAdapters(RuntimeAggregatingPlugin.java:55)
at dagger.ObjectGraph.makeGraph(ObjectGraph.java:115)
at dagger.ObjectGraph.create(ObjectGraph.java:103)
at com.oceanlife.MainApplication.onCreate(MainApplication.java:36)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:146)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:387)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:227)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Investigation to-date
I've been focusing my efforts on why "Error 1" occurred. From reading the "Compile Time Code Generation" aspect to the user guide I can see that the adapter the RuntimeAggregatingPlugin is looking for (OceanLifeModule$ModuleAdapter
) is generated at compile time. From that I've been inspecting my maven configuration and will be happy to provide that should the community feel it is necessary to figuring this out. The low level nature of "Error 2" makes me think it is more derivative than the root cause.
回答1:
Ok. So this answer pointed me down the right track.
Essentially, this was owing to the fact that my test project didn't understand what the old v1 maps component com.google.android.maps.MapActivity
etc. were. The way I resolved my problem was to import the maps.jar into my test project, i.e.;
<dependency>
<groupId>com.google.android.maps</groupId>
<artifactId>maps</artifactId>
<version>17_r3</version>
</dependency>
You can get hold of this via the Android SDK Deployer.
来源:https://stackoverflow.com/questions/17144824/robolectric-dagger-and-compile-time-moduleadapter-creation