领域单元测试



我正在尝试对 Realm 及其交互进行单元测试,但事情进展得不太顺利。我已经包含了所有依赖项并不断得到模糊的失败,下面是我对 Helper 类的代码,它是 Realm 的包装器。

问题

  1. 这是测试 Realm 的正确方法吗?

  2. 如何测试应用沙盒中的数据,该数据只能通过 UI/检测测试进行测试吗?

  3. 目前收到一个错误(如下(,并且在我得到"Powermock 零参数构造函数不存在"之前

GitHub 存储库

以下是我用于单元测试的当前代码:

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = CustomApplicationTest.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "javax.crypto.","java.security.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class,
    RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class DatabaseHelperTest {
@Rule
public PowerMockRule rule = new PowerMockRule();
private DatabaseHelper dB;
private Realm realmMock;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    mockStatic(Realm.class);
    mockStatic(RealmConfiguration.class);
    mockStatic(RealmCore.class);
    mock(DatabaseHelper.class);
    final Realm mockRealm = PowerMockito.mock(Realm.class);
    realmMock = mockRealm;
    final RealmConfiguration mockRealmConfig = PowerMockito.mock(RealmConfiguration.class);
    doNothing().when(RealmCore.class);
    RealmCore.loadLibrary(any(Context.class));
    whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
    when(Realm.getInstance(any(RealmConfiguration.class))).thenReturn(mockRealm);
    when(Realm.getDefaultInstance()).thenReturn(mockRealm);
    when(Realm.getDefaultInstance()).thenReturn(realmMock);
    when(realmMock.createObject(Person.class)).thenReturn(new Person());
    Person person = new Person();
    person.setId("2");
    person.setName("Jerry");
    person.setAge("25");
    Person person2 = new Person();
    person.setId("3");
    person.setName("Tom");
    person.setAge("22");
    List<Person> personsList = new ArrayList<>();
    personsList.add(person);
    personsList.add(person2);
    RealmQuery<Person> personRealmQuery = mockRealmQuery();
    when(realmMock.where(Person.class)).thenReturn(personRealmQuery);
    RealmResults<Person> personRealmResults = mockRealmResults();
    when(realmMock.where(Person.class).findAll()).thenReturn(personRealmResults);
    when(personRealmResults.iterator()).thenReturn(personsList.iterator());
    when(personRealmResults.size()).thenReturn(personsList.size());
    when(realmMock.copyFromRealm(personRealmResults)).thenReturn(personsList);
    realmMock = mockRealm;
    dB = new DatabaseHelper(realmMock);
}

@Test
public void insertingPerson(){
    doCallRealMethod().when(realmMock).executeTransaction(any(Realm.Transaction.class));
    Person person = mock(Person.class);
    when(realmMock.createObject(Person.class)).thenReturn(person);
    dB.putPersonData();
    verify(realmMock, times(1)).createObject(Person.class);
    verify(person, times(1)).setId(anyString());
}

@Test
public void testExistingData(){
    List<Person> personList = dB.getPersonList();
    //NPE if checking person object properties i.e name, id. Only list size is available why?
    Assert.assertEquals(2, personList.size());
}
@SuppressWarnings("unchecked")
private <T extends RealmObject> RealmQuery<T> mockRealmQuery() {
    return mock(RealmQuery.class);
}
@SuppressWarnings("unchecked")
private <T extends RealmObject> RealmResults<T> mockRealmResults() {
    return mock(RealmResults.class);
}

错误:

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to verify() is of type Realm$$EnhancerByMockitoWithCGLIB$$317bc746 and is not a mock!
Make sure you place the parenthesis correctly!
See the examples of correct verifications:
verify(mock).someMethod();
verify(mock, times(10)).someMethod();
verify(mock, atLeastOnce()).someMethod();
at com.appstronomy.realmunittesting.db.DatabaseHelperTest.insertingPerson(DatabaseHelperTest.java:133)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

这是测试 Realm 的正确方法吗?

遵循官方测试怎么样。虽然仪器测试看起来很容易,但单元测试却相当复杂:

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class ExampleActivityTest {
    // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock
    @Rule
    public PowerMockRule rule = new PowerMockRule();
    private Realm mockRealm;
    private RealmResults<Person> people;
    @Before
    public void setup() throws Exception {
        // Setup Realm to be mocked. The order of these matters
        mockStatic(RealmCore.class);
        mockStatic(RealmLog.class);
        mockStatic(Realm.class);
        mockStatic(RealmConfiguration.class);
        Realm.init(RuntimeEnvironment.application);
        // Create the mock
        final Realm mockRealm = mock(Realm.class);
        final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class);
        // TODO: Better solution would be just mock the RealmConfiguration.Builder class. But it seems there is some
        // problems for powermock to mock it (static inner class). We just mock the RealmCore.loadLibrary(Context) which
        // will be called by RealmConfiguration.Builder's constructor.
        doNothing().when(RealmCore.class);
        RealmCore.loadLibrary(any(Context.class));

        // TODO: Mock the RealmConfiguration's constructor. If the RealmConfiguration.Builder.build can be mocked, this
        // is not necessary anymore.
        whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
        // Anytime getInstance is called with any configuration, then return the mockRealm
        when(Realm.getDefaultInstance()).thenReturn(mockRealm);
        // Anytime we ask Realm to create a Person, return a new instance.
        when(mockRealm.createObject(Person.class)).thenReturn(new Person());
        // Set up some naive stubs
        Person p1 = new Person();
        p1.setAge(14);
        p1.setName("John Young");
        Person p2 = new Person();
        p2.setAge(89);
        p2.setName("John Senior");
        Person p3 = new Person();
        p3.setAge(27);
        p3.setName("Jane");
        Person p4 = new Person();
        p4.setAge(42);
        p4.setName("Robert");
        List<Person> personList = Arrays.asList(p1, p2, p3, p4);
        // Create a mock RealmQuery
        RealmQuery<Person> personQuery = mockRealmQuery();
        // When the RealmQuery performs findFirst, return the first record in the list.
        when(personQuery.findFirst()).thenReturn(personList.get(0));
        // When the where clause is called on the Realm, return the mock query.
        when(mockRealm.where(Person.class)).thenReturn(personQuery);
        // When the RealmQuery is filtered on any string and any integer, return the person query
        when(personQuery.equalTo(anyString(), anyInt())).thenReturn(personQuery);
        // RealmResults is final, must mock static and also place this in the PrepareForTest annotation array.
        mockStatic(RealmResults.class);
        // Create a mock RealmResults
        RealmResults<Person> people = mockRealmResults();
        // When we ask Realm for all of the Person instances, return the mock RealmResults
        when(mockRealm.where(Person.class).findAll()).thenReturn(people);
        // When a between query is performed with any string as the field and any int as the
        // value, then return the personQuery itself
        when(personQuery.between(anyString(), anyInt(), anyInt())).thenReturn(personQuery);
        // When a beginsWith clause is performed with any string field and any string value
        // return the same person query
        when(personQuery.beginsWith(anyString(), anyString())).thenReturn(personQuery);
        // When we ask the RealmQuery for all of the Person objects, return the mock RealmResults
        when(personQuery.findAll()).thenReturn(people);

        // The for(...) loop in Java needs an iterator, so we're giving it one that has items,
        // since the mock RealmResults does not provide an implementation. Therefore, anytime
        // anyone asks for the RealmResults Iterator, give them a functioning iterator from the
        // ArrayList of Persons we created above. This will allow the loop to execute.
        when(people.iterator()).thenReturn(personList.iterator());
        // Return the size of the mock list.
        when(people.size()).thenReturn(personList.size());
        this.mockRealm = mockRealm;
        this.people = people;
    }

    @Test
    public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() {
        doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class));
        // Create activity
        ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get();
        assertThat(activity.getTitle().toString(), is("Unit Test Example"));
        // Verify that two Realm.getInstance() calls took place.
        verifyStatic(times(2));
        Realm.getDefaultInstance();
        // verify that we have four begin and commit transaction calls
        // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
        //verify(mockRealm, times(4)).executeTransaction(Mockito.any(Realm.Transaction.class));
        // Click the clean up button
        activity.findViewById(R.id.clean_up).performClick();
        // Verify that begin and commit transaction were called (been called a total of 5 times now)
        // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
        //verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class));
        // Verify that we queried for Person instances five times in this run (2 in basicCrud(),
        // 2 in complexQuery() and 1 in the button click)
        verify(mockRealm, times(5)).where(Person.class);
        // Verify that the delete method was called. Delete is also called in the start of the
        // activity to ensure we start with a clean db.
        verify(mockRealm, times(2)).delete(Person.class);
        // Call the destroy method so we can verify that the .close() method was called (below)
        activity.onDestroy();
        // Verify that the realm got closed 2 separate times. Once in the AsyncTask, once
        // in onDestroy
        verify(mockRealm, times(2)).close();
    }

较早的答案

https://medium.com/@q2ad/android-testing-realm-2dc1e1c94ee1 有一个很好的建议:不要模拟 Realm,而是使用临时实例。带有依赖注入的原始命题:使用

RealmConfiguration testConfig = 
   new RealmConfiguration.Builder().
      inMemory().
      name("test-realm").build();
Realm testRealm = Realm.getInstance(testConfig);

如果无法进行依赖注入,则可以使用

Realm.setDefaultConfiguration(testConfig);

相反,它设置Realm.getDefaultInstance()返回的Realm


编辑:如果您收到java.lang.IllegalStateException,请记住事先致电Realm.init(InstrumentationRegistry.getTargetContext()),并将文件放在android-test目录中。(即:使用检测测试,而不是单元测试(。

我的目标是测试依赖于 Realm 的业务逻辑。Realm.init 不断抛出异常,例如:

java.lang.IllegalStateException: Context.getFilesDir() returns /data/user/0/com.pornhub.android.test/files which is not an existing directory. See https://issuetracker.google.com/issues/36918154
at io.realm.Realm.checkFilesDirAvailable(Realm.java:256)
at io.realm.Realm.init(Realm.java:199)
at com.headcheckhealth.headcheck.GenericRealmTest.<init>(RealmTest.kt:99)
...

要让 Realm 工作,请使用 getTargetContext() 而不是 getContext()

package com.github.ericytsang
import android.app.Instrumentation
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import io.realm.RealmConfiguration
import org.junit.Test
/**
 * tests to see how [Realm] works. nothing domain-specific here.
 *
 * cannot use Robolectric because https://github.com/robolectric/robolectric/issues/1389 :(
 * need to use Android instrumented tests.
 */
class GenericRealmTest {
    /**
     * [Realm.init] needs [Instrumentation.getTargetContext] to work; [Instrumentation.getContext]
     * will not work.
     */
    private val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val db = run {
        Realm.init(context)
        Realm.getInstance(
            RealmConfiguration.Builder()
                .inMemory()
                .name("realm.db")
                .build()
        )
    }
    @Test
    fun teardown_works() = Unit
}

相关内容

  • 没有找到相关文章

最新更新