【问题标题】:Android, Room fail on unit testing with RobolectricAndroid,Room 在使用 Robolectric 进行单元测试时失败
【发布时间】:2020-03-17 10:58:55
【问题描述】:

我正在尝试为视图模型创建一些测试。

视图模型包括一个调用数据库的数据库实例

CallRoomDatabase db = CallRoomDatabase.getDatabase(application);

getDatabase 获取 Dao() 的一个实例

  @Database(entities = {CallEntity.class}, version = 1)
 public abstract class CallRoomDatabase extends RoomDatabase {

public abstract CDao cDao();

// marking the instance as volatile to ensure atomic access to the variable
private static volatile CRoomDatabase INSTANCE;

public static CRoomDatabase getDatabase(final Context context) {
    if (INSTANCE == null) {
        synchronized (CallRoomDatabase.class) {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        CRoomDatabase.class, "database")
                        // Wipes and rebuilds instead of migrating if no Migration object.
                        // Migration is not part of this codelab.
                        .fallbackToDestructiveMigration()
                        .addCallback(sRoomDatabaseCallback)
                        .build();
            }
        }
    }
    return INSTANCE;
}

但是,当尝试在测试中实例化模型时,我在上面的 .build() 上收到错误

  java.lang.NullPointerException
at androidx.room.Room.getGeneratedImplementation(Room.java:79)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:952)
at com.s.o.dbutils.CRoomDatabase.getDatabase(CRoomDatabase.java:32)
at com.s.o.viewmodels.CViewModelTest.checkForNuTest(CViewModelTest.kt:66)
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.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.powermock.modules.junit4.rule.PowerMockStatement$1.run(PowerMockRule.java:83)
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)

RoomDatabase.java

            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);

Room.javaklassnull

        static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {

我试过在测试中根本不使用模型,只是用实例化数据库

          val db = CRoomDatabase.getDatabase(ApplicationProvider.getApplicationContext())

仍然在同一处中断.build()

任何想法如何克服该异常?

我们实际上不需要以某种方式测试数据库,只需要该模型中包含的一些功能,所以一些避免错误的方法就足够了。

【问题讨论】:

  • 我现在遇到了同样的问题。我猜,你的数据库包没有加载到MockClassLoader。有趣的是,当您为 Test 类定义成员变量(lateinit val db: CallRoomDatabase)时,即使该变量没有分配任何对象,也不会出错。

标签: android unit-testing kotlin android-room robolectric


【解决方案1】:

Room 应该作为 Android Instrumentation Test 进行测试,而不是 JUnit 测试。

更新:

使用mockito 模拟数据库和任何存储库。

使用 mockito 方法,例如:

  • @Mock - 模拟注解来模拟全局和局部变量。
  • mock - 在线启动类的模拟。
  • when - 为 mock 配置返回行为。
  • verify - 在 mocks 及其方法上断言交互。
  • - 与 verify 一起使用来断言对模拟及其方法的调用次数。
  • any - 一个参数匹配器,用于向模拟断言提供的值。

使用 Mockito,例如。我不知道你的代码是如何工作的,我做了一些假设,比如 Call.class 的存在以及你需要什么模拟:

@Mock private CallRoomDatabase database;
@Mock private CRoomDatabase cRoomDatabase;
@Mock private CDao cDao;

private MyClass myClass;

@Before 
public void setUp() {
    // initiate all globally defined mocks annotated with @Mock
    initMocks(this);

    // Setup our expected behaviour from the mock
    when(database.getDatabase()).thenReturn(cRoomDatabase);
    myClass = new myClass(database, cDao);
}

@Test 
public void givenSomeTest_whenCallingGetCall_thenInsertNewCall() {
    // Setup our expected behaviour from the mock
    when(cDao.getCall()).thenReturn(mock(Call.class));

    // do test

    // assert mock interaction with arguments and number of expected calls
    verify(cDao, times(1)).insertCall(any(mock(Call.class)))
}

【讨论】:

  • 我不想测试 Room,我想克服它。
  • 很好,您可以使用 Mockto 作为模拟框架来存根数据库并返回指定值。更新了我的答案。
  • 你能用一个关于如何使用 mockito 模拟数据库的例子来更新你的答案吗?
  • 添加了一些例子和解释。
  • 我不知道影子是什么。如果您需要更多关于 mockito 的帮助,在线上有大量示例,因此希望这可以帮助您入门。享受吧!
【解决方案2】:

为了独立于数据库测试程序,我建议实现 2 个存储库:1 个用于项目的存储库,1 个用于测试,以及用于它们的通用接口。您可以使用 Dagger 2 来实现这一点。

【讨论】:

  • 你有什么好的文档或样本可以为这个策略@Destroyer 提供吗?一个快速的替代方法是使用 MockK 或 Mockito 模拟存储库,就像 Coinverse 的单元测试一样?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多