Android,使用Robolectric进行单元测试时,Room出现失败

4

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

该视图模型包括一个数据库实例化,调用了一个数据库:

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.java 中,klassnull

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

我尝试在测试中根本不使用模型,只使用以下方法实例化数据库:

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

仍然在相同的.build()处发生错误。

有任何想法如何避免这个异常吗?

实际上我们并不需要对数据库进行测试,只需要在该模型中包含一些函数,因此只需找到一种方法来避免错误即可。


我现在遇到了同样的问题。我猜测你的数据库包没有加载到MockClassLoader中。有趣的是,当你在测试类中定义一个成员变量(lateinit val db: CallRoomDatabase)时,即使该变量没有分配任何对象,也不会出现错误。 - MinseongPark
2个回答

4

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

更新:

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

利用mockito方法:

  • @Mock - 用于模拟全局和局部变量的模拟注释。
  • mock - 在线初始化类的模拟。
  • when - 配置模拟的返回行为。
  • verify - 断言模拟及其方法的交互。
  • times - 与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,我想克服它。 - thahgr
非常好,您可以使用Mockto作为一个模拟框架来存根数据库并返回指定的值。我已经更新了我的答案。 - JakeB
1
添加了一些示例和解释。 - JakeB
谢谢更新。 使用Shadow到RoomDatabase?你觉得怎么样? - thahgr
1
我不知道Shadow是什么。如果你需要在mockito方面更多的帮助,网上有大量的例子,希望这能帮助你入门。享受吧! - JakeB
显示剩余2条评论

0
为了从数据库中隔离测试程序,我建议实现2个存储库:一个用于项目,一个用于测试,并为它们实现一个公共接口。您可以使用Dagger 2来实现这一点。

@Destroyer,你有没有提供这个策略的好文档或示例?还有一个快速的替代方法是使用MockK或Mockito模拟存储库,就像在Coinverse的单元测试中所做的那样。 - AdamHurwitz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接