Realm Unit Testing

Is this the correct way of testing Realm?

How about following the official tests. While instrumentation tests seem easy, unit test are quite involved:

@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();
    }

OLDER ANSWER

https://medium.com/@q2ad/android-testing-realm-2dc1e1c94ee1 has a great proposal: do not mock Realm, but use a temporary instance instead. Original proposition with dependency injection: Use

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

Realm testRealm = Realm.getInstance(testConfig);

If dependency injection is not possible, you could use

Realm.setDefaultConfiguration(testConfig);

instead, which sets the Realm returned by Realm.getDefaultInstance().


EDIT: If you receive a java.lang.IllegalStateException, remember to call Realm.init(InstrumentationRegistry.getTargetContext()) beforehand, and put the files inside the android-test directory. (that is: use an instrumentation test, not a unit test).