使用 Robolectric 和 Mockito 对 Realm + Dagger 2 进行单元测试
Unit testing Realm + Dagger 2 with Robolectric & Mockito
所以我正在开发这个使用 Dagger 2 进行依赖注入并将 Realm 作为数据库的小项目。
我正在使用 Robolectric 和 Mockito(使用 Powermock)对其进行单元测试。从之前的研究(和很多痛苦)中,我意识到测试 Realm 非常费力,但过去已经完成 here。
现在,我的项目的设置和结构与上面链接的项目非常相似。
当我 运行 我的单元测试时,所有单元测试都通过了,除了给我一个非常神秘的消息,如下所示:
java.lang.NullPointerException
at org.robolectric.internal.ShadowExtractor.extract(ShadowExtractor.java:5)
at org.robolectric.Shadows.shadowOf(Shadows.java:1190)
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:37)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:31)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:23)
at org.robolectric.util.ActivityController.<init>(ActivityController.java:40)
at org.robolectric.util.ActivityController.of(ActivityController.java:32)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:82)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:78)
at org.robolectric.Robolectric.setupActivity(Robolectric.java:86)
at uk.co.placona.tradesafe.view.EditActivityTest.ActivityShouldNotBeNull(EditActivityTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl
上述错误指定的代码行是:
activity = Robolectric.setupActivity(EditActivity.class);
activity 存在,并且在启动时注入了一个 TradeRepository。
有问题的 activity 可以与其余代码一起找到 here。我一直在尝试调试它大约 3 天,但没有成功。我创建的所有其他单元测试都工作正常,除了 Activity 使用的任何单元测试,这让我觉得我可能遗漏了一些非常明显的东西。
很乐意在这里澄清任何问题。非常感谢!
static 是邪恶的,powermock 是邪恶的:)。
我认为你应该摆脱你的 class 注射器。您不需要它,因为您在应用程序的生命周期内只有一个 CustomApplication 对象。
您应该按如下方式修改您的代码:
在CustomApplication.java中创建应用组件,设置在字段变量中,注入
private ApplicationComponent applicationComponent;
public void setup(){
getOrCreateApplicationComponent().inject(this);
databaseRealm.setup();
stethoDebug.setup(this);
}
public ApplicationComponent getOrCreateApplicationComponent() {
if (applicationComponent == null) {
applicationComponent = DaggerApplicationComponent.builder()
.applicationContextModule(new ApplicationContextModule(this))
.repositoryModule(new RepositoryModule())
.build();
}
return applicationComponent;
}
CreateActivity、EditActivity、MainActivity的onCreate方法中,Injector替换为
((CustomApplication) getApplication())
.getOrCreateApplicationComponent()
.inject(this);
在 RepositoryModule 中,我们将使用 Dagger 2 将依赖项注入构造函数,因此我们不需要手动注入 Context 和 DatabaseRealm
@Provides
@Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return new TradeRepositoryImpl(databaseRealm);
}
@Provides
@Singleton
public DatabaseRealm provideDatabaseRealm(Context context) {
return new DatabaseRealm(context);
}
然后在 DatabaseRealm 中我们添加一个以 Context 作为参数的构造函数
Context mContext;
RealmConfiguration realmConfiguration;
public DatabaseRealm(Context context) {
mContext = context;
}
与 TradeRepositoryImpl 相同,添加了带有 databaseRealm 的构造函数
DatabaseRealm databaseRealm;
public TradeRepositoryImpl(DatabaseRealm databaseRealm) {
this.databaseRealm = databaseRealm;
}
对于 RepositoryTestModule,我们添加 databaseRealm 作为参数:
@Provides
@Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return isMocked ? mock(TradeRepository.class) : new TradeRepositoryImpl(databaseRealm);
}
在您的 TestCustomApplication 中,我们覆盖了 getOrCreateApplicationComponent
@Override
public ApplicationComponent getOrCreateApplicationComponent() {
return DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build();
}
现在,对于您的每个测试,我们 运行 使用 RobolectricGradleTestRunner 并添加 TestCustomApplication.class 作为应用程序标签
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = TestCustomApplication.class)
当我们需要将依赖项注入我们的测试时,我们将像这样注入:
@Before
public void setupDagger() {
DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build().inject(this);
}
我们的 EditActivityTest 中仍然有一个 NullPointerException,因为这一行:
loadTrade(intent.getExtras().getString("ID"));
要么检查意图不为空,要么在测试中提供一个。
查看您的测试 class,我没有看到您使用任何 Robolectric 测试运行程序。
您必须使用 RobolectricGradleTestRunner
或 RobolectricTestRunner
来触发 Robolecric 的加载清单、解析资源、创建主循环程序等功能。
如果你不使用它们,你可能可以在设置中用自己的代码实现它,但这不是通常的方法,我不确定这里有多少人可以向你解释如何实现它.
此外,Robolectric 和 PowerMock 都在修改 java ClassLoader
。这就是为什么很难(也许不可能)让他们一起工作的原因。因此,请检查@Steve 回答如何修改代码以删除 PowerMock 测试的必要性。
所以我正在开发这个使用 Dagger 2 进行依赖注入并将 Realm 作为数据库的小项目。
我正在使用 Robolectric 和 Mockito(使用 Powermock)对其进行单元测试。从之前的研究(和很多痛苦)中,我意识到测试 Realm 非常费力,但过去已经完成 here。
现在,我的项目的设置和结构与上面链接的项目非常相似。
当我 运行 我的单元测试时,所有单元测试都通过了,除了给我一个非常神秘的消息,如下所示:
java.lang.NullPointerException
at org.robolectric.internal.ShadowExtractor.extract(ShadowExtractor.java:5)
at org.robolectric.Shadows.shadowOf(Shadows.java:1190)
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:37)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:31)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:23)
at org.robolectric.util.ActivityController.<init>(ActivityController.java:40)
at org.robolectric.util.ActivityController.of(ActivityController.java:32)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:82)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:78)
at org.robolectric.Robolectric.setupActivity(Robolectric.java:86)
at uk.co.placona.tradesafe.view.EditActivityTest.ActivityShouldNotBeNull(EditActivityTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl
上述错误指定的代码行是:
activity = Robolectric.setupActivity(EditActivity.class);
activity 存在,并且在启动时注入了一个 TradeRepository。
有问题的 activity 可以与其余代码一起找到 here。我一直在尝试调试它大约 3 天,但没有成功。我创建的所有其他单元测试都工作正常,除了 Activity 使用的任何单元测试,这让我觉得我可能遗漏了一些非常明显的东西。
很乐意在这里澄清任何问题。非常感谢!
static 是邪恶的,powermock 是邪恶的:)。
我认为你应该摆脱你的 class 注射器。您不需要它,因为您在应用程序的生命周期内只有一个 CustomApplication 对象。
您应该按如下方式修改您的代码:
在CustomApplication.java中创建应用组件,设置在字段变量中,注入
private ApplicationComponent applicationComponent;
public void setup(){
getOrCreateApplicationComponent().inject(this);
databaseRealm.setup();
stethoDebug.setup(this);
}
public ApplicationComponent getOrCreateApplicationComponent() {
if (applicationComponent == null) {
applicationComponent = DaggerApplicationComponent.builder()
.applicationContextModule(new ApplicationContextModule(this))
.repositoryModule(new RepositoryModule())
.build();
}
return applicationComponent;
}
CreateActivity、EditActivity、MainActivity的onCreate方法中,Injector替换为
((CustomApplication) getApplication())
.getOrCreateApplicationComponent()
.inject(this);
在 RepositoryModule 中,我们将使用 Dagger 2 将依赖项注入构造函数,因此我们不需要手动注入 Context 和 DatabaseRealm
@Provides
@Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return new TradeRepositoryImpl(databaseRealm);
}
@Provides
@Singleton
public DatabaseRealm provideDatabaseRealm(Context context) {
return new DatabaseRealm(context);
}
然后在 DatabaseRealm 中我们添加一个以 Context 作为参数的构造函数
Context mContext;
RealmConfiguration realmConfiguration;
public DatabaseRealm(Context context) {
mContext = context;
}
与 TradeRepositoryImpl 相同,添加了带有 databaseRealm 的构造函数
DatabaseRealm databaseRealm;
public TradeRepositoryImpl(DatabaseRealm databaseRealm) {
this.databaseRealm = databaseRealm;
}
对于 RepositoryTestModule,我们添加 databaseRealm 作为参数:
@Provides
@Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return isMocked ? mock(TradeRepository.class) : new TradeRepositoryImpl(databaseRealm);
}
在您的 TestCustomApplication 中,我们覆盖了 getOrCreateApplicationComponent
@Override
public ApplicationComponent getOrCreateApplicationComponent() {
return DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build();
}
现在,对于您的每个测试,我们 运行 使用 RobolectricGradleTestRunner 并添加 TestCustomApplication.class 作为应用程序标签
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = TestCustomApplication.class)
当我们需要将依赖项注入我们的测试时,我们将像这样注入:
@Before
public void setupDagger() {
DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build().inject(this);
}
我们的 EditActivityTest 中仍然有一个 NullPointerException,因为这一行:
loadTrade(intent.getExtras().getString("ID"));
要么检查意图不为空,要么在测试中提供一个。
查看您的测试 class,我没有看到您使用任何 Robolectric 测试运行程序。
您必须使用 RobolectricGradleTestRunner
或 RobolectricTestRunner
来触发 Robolecric 的加载清单、解析资源、创建主循环程序等功能。
如果你不使用它们,你可能可以在设置中用自己的代码实现它,但这不是通常的方法,我不确定这里有多少人可以向你解释如何实现它.
此外,Robolectric 和 PowerMock 都在修改 java ClassLoader
。这就是为什么很难(也许不可能)让他们一起工作的原因。因此,请检查@Steve 回答如何修改代码以删除 PowerMock 测试的必要性。