使用 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 测试运行程序。

您必须使用 RobolectricGradleTestRunnerRobolectricTestRunner 来触发 Robolecric 的加载清单、解析资源、创建主循环程序等功能。

如果你不使用它们,你可能可以在设置中用自己的代码实现它,但这不是通常的方法,我不确定这里有多少人可以向你解释如何实现它.

此外,RobolectricPowerMock 都在修改 java ClassLoader。这就是为什么很难(也许不可能)让他们一起工作的原因。因此,请检查@Steve 回答如何修改代码以删除 PowerMock 测试的必要性。