【问题标题】:Unit testing Realm + Dagger 2 with Robolectric & Mockito使用 Robolectric 和 Mockito 对 Realm + Dagger 2 进行单元测试
【发布时间】:2016-11-13 14:11:40
【问题描述】:

所以我正在开发这个使用 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。

可以在here 中找到相关活动以及其余代码。我已经尝试调试了大约 3 天,但没有成功。我创建的所有其他单元测试都可以正常工作,除了 Activity 使用的任何单元测试,这让我觉得我可能遗漏了一些非常明显的东西。

很乐意在这里澄清任何问题。非常感谢!

【问题讨论】:

标签: android unit-testing realm robolectric dagger-2


【解决方案1】:

static 是邪恶的,powermock 是邪恶的 :)。

我认为你应该摆脱你的类 Injector。您不需要它,因为在应用程序的生命周期中您只有一个 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"));

要么检查意图不为空,要么在测试中提供一个。

【讨论】:

  • 我通常会创建Test&lt;MyApplication&gt;,它将由 Robolectric 自动加载,以避免使用 CustomTestApplication 和重复设置注入的逻辑。但可能是超级好主意。我会重新考虑我的方法
  • 哇!你刚刚带我去学校,史蒂夫!非常感谢你做的这些。通读代码,它更有意义,看起来更干净!是的..静态的..告诉我吧!我希望有办法让我再+1这个答案!如此伟大而完整的答案!非常感谢您抽出宝贵的时间,我真的很感激!
【解决方案2】:

查看您的测试类,我没有看到您使用任何 Robolectric 测试运行器。

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

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

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

【讨论】:

  • 感谢 Eugen,非常感谢您在这里的回答。我已经在很多方面修改了这段代码,我在某些时候改变了跑步者,甚至没有意识到这一点。知道什么时候你开始随心所欲地改变一切,看看是否有什么不同吗?那是我昨天:-)。非常感谢您的 cmets 和建议!
  • 不客气!感谢@steve-c 的评论和赞誉
猜你喜欢
  • 2016-06-15
  • 1970-01-01
  • 2015-07-11
  • 1970-01-01
  • 1970-01-01
  • 2017-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多